mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-25 20:16:00 +01:00
Refactor World-Management and Region/Chunk-Loading (#496)
* Implement PackedIntArrayAccess * First working render with BlueNBT * Progress converting chunkloaders * Core rewrite done * WIP - Restructuring configs and world-map mapping * WIP - Compiling and starting without exceptions :) * Fix cave detection * Ensure configuration backwards compatibility (resolve dimension from configured world if missing) * Implement support for 1.16+ chunks * Implement support for 1.15+ chunks * Implement support for 1.13+ chunks and some fixes * Also find worlds based on their id again in BlueMapAPI * Improve autogenerated config names * Implement equals for all ServerWorld implementations * Get rid of var usage
This commit is contained in:
parent
efd45658d5
commit
16981f2797
@ -33,6 +33,9 @@ dependencies {
|
|||||||
api ("de.bluecolored.bluemap.core:BlueMapCore")
|
api ("de.bluecolored.bluemap.core:BlueMapCore")
|
||||||
|
|
||||||
compileOnly ("org.jetbrains:annotations:16.0.2")
|
compileOnly ("org.jetbrains:annotations:16.0.2")
|
||||||
|
compileOnly ("org.projectlombok:lombok:1.18.28")
|
||||||
|
|
||||||
|
annotationProcessor ("org.projectlombok:lombok:1.18.28")
|
||||||
|
|
||||||
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
|
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
|
||||||
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
||||||
|
@ -26,10 +26,16 @@
|
|||||||
|
|
||||||
import de.bluecolored.bluemap.common.config.*;
|
import de.bluecolored.bluemap.common.config.*;
|
||||||
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
||||||
|
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface BlueMapConfigProvider {
|
public interface BlueMapConfiguration {
|
||||||
|
|
||||||
|
MinecraftVersion getMinecraftVersion();
|
||||||
|
|
||||||
CoreConfig getCoreConfig();
|
CoreConfig getCoreConfig();
|
||||||
|
|
||||||
WebappConfig getWebappConfig();
|
WebappConfig getWebappConfig();
|
||||||
@ -42,4 +48,8 @@ public interface BlueMapConfigProvider {
|
|||||||
|
|
||||||
Map<String, StorageConfig> getStorageConfigs();
|
Map<String, StorageConfig> getStorageConfigs();
|
||||||
|
|
||||||
|
@Nullable Path getResourcePacksFolder();
|
||||||
|
|
||||||
|
@Nullable Path getModsFolder();
|
||||||
|
|
||||||
}
|
}
|
@ -36,17 +36,17 @@
|
|||||||
import de.bluecolored.bluemap.common.config.MapConfig;
|
import de.bluecolored.bluemap.common.config.MapConfig;
|
||||||
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
|
||||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||||
import de.bluecolored.bluemap.core.debug.StateDumper;
|
import de.bluecolored.bluemap.core.debug.StateDumper;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
import de.bluecolored.bluemap.core.resources.datapack.DataPack;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.spongepowered.configurate.ConfigurateException;
|
import org.spongepowered.configurate.ConfigurateException;
|
||||||
@ -60,7 +60,6 @@
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@ -72,85 +71,32 @@
|
|||||||
@DebugDump
|
@DebugDump
|
||||||
public class BlueMapService implements Closeable {
|
public class BlueMapService implements Closeable {
|
||||||
|
|
||||||
private final ServerInterface serverInterface;
|
private final BlueMapConfiguration config;
|
||||||
private final BlueMapConfigProvider configs;
|
private final WebFilesManager webFilesManager;
|
||||||
|
|
||||||
private final Map<Path, String> worldIds;
|
|
||||||
private final Map<String, Storage> storages;
|
|
||||||
|
|
||||||
private volatile WebFilesManager webFilesManager;
|
|
||||||
|
|
||||||
private Map<String, World> worlds;
|
|
||||||
private Map<String, BmMap> maps;
|
|
||||||
|
|
||||||
private ResourcePack resourcePack;
|
private ResourcePack resourcePack;
|
||||||
|
private final Map<String, World> worlds;
|
||||||
|
private final Map<String, BmMap> maps;
|
||||||
|
private final Map<String, Storage> storages;
|
||||||
|
|
||||||
|
|
||||||
public BlueMapService(ServerInterface serverInterface, BlueMapConfigProvider configProvider, @Nullable ResourcePack preloadedResourcePack) {
|
public BlueMapService(BlueMapConfiguration configuration, @Nullable ResourcePack preloadedResourcePack) {
|
||||||
this(serverInterface, configProvider);
|
this(configuration);
|
||||||
|
|
||||||
if (preloadedResourcePack != null)
|
|
||||||
this.resourcePack = preloadedResourcePack;
|
this.resourcePack = preloadedResourcePack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlueMapService(ServerInterface serverInterface, BlueMapConfigProvider configProvider) {
|
public BlueMapService(BlueMapConfiguration configuration) {
|
||||||
this.serverInterface = serverInterface;
|
this.config = configuration;
|
||||||
this.configs = configProvider;
|
this.webFilesManager = new WebFilesManager(config.getWebappConfig().getWebroot());
|
||||||
|
|
||||||
this.worldIds = new ConcurrentHashMap<>();
|
this.worlds = new ConcurrentHashMap<>();
|
||||||
this.storages = new HashMap<>();
|
this.maps = new ConcurrentHashMap<>();
|
||||||
|
this.storages = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
StateDumper.global().register(this);
|
StateDumper.global().register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getWorldId(Path worldFolder) throws IOException {
|
|
||||||
// fast-path
|
|
||||||
String id = worldIds.get(worldFolder);
|
|
||||||
if (id != null) return id;
|
|
||||||
|
|
||||||
// second try with normalized absolute path
|
|
||||||
worldFolder = worldFolder.toAbsolutePath().normalize();
|
|
||||||
id = worldIds.get(worldFolder);
|
|
||||||
if (id != null) return id;
|
|
||||||
|
|
||||||
// secure (slower) query with real path
|
|
||||||
worldFolder = worldFolder.toRealPath();
|
|
||||||
id = worldIds.get(worldFolder);
|
|
||||||
if (id != null) return id;
|
|
||||||
|
|
||||||
synchronized (worldIds) {
|
|
||||||
// check again if another thread has already added the world
|
|
||||||
id = worldIds.get(worldFolder);
|
|
||||||
if (id != null) return id;
|
|
||||||
|
|
||||||
Logger.global.logDebug("Loading world id for '" + worldFolder + "'...");
|
|
||||||
|
|
||||||
// now we can be sure it wasn't loaded yet .. load
|
|
||||||
Path idFile = worldFolder.resolve("bluemap.id");
|
|
||||||
if (!Files.exists(idFile)) {
|
|
||||||
id = this.serverInterface.getWorld(worldFolder)
|
|
||||||
.flatMap(ServerWorld::getId)
|
|
||||||
.orElse(UUID.randomUUID().toString());
|
|
||||||
Files.writeString(idFile, id, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
|
||||||
|
|
||||||
worldIds.put(worldFolder, id);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
id = Files.readString(idFile);
|
|
||||||
worldIds.put(worldFolder, id);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebFilesManager getWebFilesManager() {
|
public WebFilesManager getWebFilesManager() {
|
||||||
if (webFilesManager == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (webFilesManager == null)
|
|
||||||
webFilesManager = new WebFilesManager(configs.getWebappConfig().getWebroot());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return webFilesManager;
|
return webFilesManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,13 +110,13 @@ public synchronized void createOrUpdateWebApp(boolean force) throws Configuratio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update settings.json
|
// update settings.json
|
||||||
if (!configs.getWebappConfig().isUpdateSettingsFile()) {
|
if (!config.getWebappConfig().isUpdateSettingsFile()) {
|
||||||
webFilesManager.loadSettings();
|
webFilesManager.loadSettings();
|
||||||
webFilesManager.addFrom(configs.getWebappConfig());
|
webFilesManager.addFrom(config.getWebappConfig());
|
||||||
} else {
|
} else {
|
||||||
webFilesManager.setFrom(configs.getWebappConfig());
|
webFilesManager.setFrom(config.getWebappConfig());
|
||||||
}
|
}
|
||||||
for (String mapId : configs.getMapConfigs().keySet()) {
|
for (String mapId : config.getMapConfigs().keySet()) {
|
||||||
webFilesManager.addMap(mapId);
|
webFilesManager.addMap(mapId);
|
||||||
}
|
}
|
||||||
webFilesManager.saveSettings();
|
webFilesManager.saveSettings();
|
||||||
@ -180,32 +126,45 @@ public synchronized void createOrUpdateWebApp(boolean force) throws Configuratio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Map<String, World> getWorlds() throws InterruptedException {
|
/**
|
||||||
return getWorlds(mapId -> true);
|
* Gets all loaded maps.
|
||||||
|
* @return A map of loaded maps
|
||||||
|
*/
|
||||||
|
public Map<String, BmMap> getMaps() {
|
||||||
|
return Collections.unmodifiableMap(maps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Map<String, World> getWorlds(Predicate<String> mapFilter) throws InterruptedException {
|
/**
|
||||||
if (worlds == null) loadWorldsAndMaps(mapFilter);
|
* Gets all loaded worlds.
|
||||||
return worlds;
|
* @return A map of loaded worlds
|
||||||
|
*/
|
||||||
|
public Map<String, World> getWorlds() {
|
||||||
|
return Collections.unmodifiableMap(worlds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Map<String, BmMap> getMaps() throws InterruptedException {
|
/**
|
||||||
return getMaps(mapId -> true);
|
* Gets or loads configured maps.
|
||||||
|
* @return A map of loaded maps
|
||||||
|
*/
|
||||||
|
public Map<String, BmMap> getOrLoadMaps() throws InterruptedException {
|
||||||
|
return getOrLoadMaps(mapId -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Map<String, BmMap> getMaps(Predicate<String> mapFilter) throws InterruptedException {
|
/**
|
||||||
if (maps == null) loadWorldsAndMaps(mapFilter);
|
* Gets or loads configured maps.
|
||||||
return maps;
|
* @param filter A predicate filtering map-ids that should be loaded
|
||||||
}
|
* (if maps are already loaded, they will be returned as well)
|
||||||
|
* @return A map of all loaded maps
|
||||||
|
*/
|
||||||
|
public synchronized Map<String, BmMap> getOrLoadMaps(Predicate<String> filter) throws InterruptedException {
|
||||||
|
for (var entry : config.getMapConfigs().entrySet()) {
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
private synchronized void loadWorldsAndMaps(Predicate<String> mapFilter) throws InterruptedException {
|
if (!filter.test(entry.getKey())) continue;
|
||||||
maps = new HashMap<>();
|
if (maps.containsKey(entry.getKey())) continue;
|
||||||
worlds = new HashMap<>();
|
|
||||||
|
|
||||||
for (var entry : configs.getMapConfigs().entrySet()) {
|
|
||||||
if (!mapFilter.test(entry.getKey())) continue;
|
|
||||||
try {
|
try {
|
||||||
loadMapConfig(entry.getKey(), entry.getValue());
|
loadMap(entry.getKey(), entry.getValue());
|
||||||
} catch (ConfigurationException ex) {
|
} catch (ConfigurationException ex) {
|
||||||
Logger.global.logWarning(ex.getFormattedExplanation());
|
Logger.global.logWarning(ex.getFormattedExplanation());
|
||||||
Throwable cause = ex.getRootCause();
|
Throwable cause = ex.getRootCause();
|
||||||
@ -214,16 +173,15 @@ private synchronized void loadWorldsAndMaps(Predicate<String> mapFilter) throws
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Collections.unmodifiableMap(maps);
|
||||||
worlds = Collections.unmodifiableMap(worlds);
|
|
||||||
maps = Collections.unmodifiableMap(maps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void loadMapConfig(String id, MapConfig mapConfig) throws ConfigurationException, InterruptedException {
|
private synchronized void loadMap(String id, MapConfig mapConfig) throws ConfigurationException, InterruptedException {
|
||||||
String name = mapConfig.getName();
|
String name = mapConfig.getName();
|
||||||
if (name == null) name = id;
|
if (name == null) name = id;
|
||||||
|
|
||||||
Path worldFolder = mapConfig.getWorld();
|
Path worldFolder = mapConfig.getWorld();
|
||||||
|
Key dimension = mapConfig.getDimension();
|
||||||
|
|
||||||
// if there is no world configured, we assume the map is static, or supplied from a different server
|
// if there is no world configured, we assume the map is static, or supplied from a different server
|
||||||
if (worldFolder == null) {
|
if (worldFolder == null) {
|
||||||
@ -231,48 +189,63 @@ private synchronized void loadMapConfig(String id, MapConfig mapConfig) throws C
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if there is no dimension configured, we assume world-folder is actually the dimension-folder and convert (backwards compatibility)
|
||||||
|
if (dimension == null) {
|
||||||
|
worldFolder = worldFolder.normalize();
|
||||||
|
if (worldFolder.endsWith("DIM-1")) {
|
||||||
|
worldFolder = worldFolder.getParent();
|
||||||
|
dimension = DataPack.DIMENSION_THE_NETHER;
|
||||||
|
} else if (worldFolder.endsWith("DIM1")) {
|
||||||
|
worldFolder = worldFolder.getParent();
|
||||||
|
dimension = DataPack.DIMENSION_THE_END;
|
||||||
|
} else if (
|
||||||
|
worldFolder.getNameCount() > 3 &&
|
||||||
|
worldFolder.getName(worldFolder.getNameCount() - 3).equals(Path.of("dimensions"))
|
||||||
|
) {
|
||||||
|
String namespace = worldFolder.getName(worldFolder.getNameCount() - 2).toString();
|
||||||
|
String value = worldFolder.getName(worldFolder.getNameCount() - 1).toString();
|
||||||
|
worldFolder = worldFolder.subpath(0, worldFolder.getNameCount() - 3);
|
||||||
|
dimension = new Key(namespace, value);
|
||||||
|
} else {
|
||||||
|
dimension = DataPack.DIMENSION_OVERWORLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.global.logInfo("The map '" + name + "' has no dimension configured.\n" +
|
||||||
|
"Assuming world: '" + worldFolder + "' and dimension: '" + dimension + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!Files.isDirectory(worldFolder)) {
|
if (!Files.isDirectory(worldFolder)) {
|
||||||
throw new ConfigurationException(
|
throw new ConfigurationException(
|
||||||
"'" + 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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
String worldId;
|
String worldId = MCAWorld.id(worldFolder, dimension);
|
||||||
try {
|
|
||||||
worldId = getWorldId(worldFolder);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new ConfigurationException(
|
|
||||||
"Could not load the ID for the world (" + worldFolder.toAbsolutePath().normalize() + ")!\n" +
|
|
||||||
"Make sure BlueMap has read and write access/permissions to the world-files for this map.",
|
|
||||||
ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
World world = worlds.get(worldId);
|
World world = worlds.get(worldId);
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
try {
|
try {
|
||||||
Logger.global.logInfo("Loading world '" + worldId + "' (" + worldFolder.toAbsolutePath().normalize() + ")...");
|
Logger.global.logDebug("Loading world " + worldId + " ...");
|
||||||
world = new MCAWorld(worldFolder, mapConfig.getWorldSkyLight(), mapConfig.isIgnoreMissingLightData());
|
world = MCAWorld.load(worldFolder, dimension);
|
||||||
worlds.put(worldId, world);
|
worlds.put(worldId, world);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new ConfigurationException(
|
throw new ConfigurationException(
|
||||||
"Failed to load world '" + worldId + "' (" + worldFolder.toAbsolutePath().normalize() + ")!\n" +
|
"Failed to load world " + worldId + "!\n" +
|
||||||
"Is the level.dat of that world present and not corrupted?",
|
"Is the level.dat of that world present and not corrupted?",
|
||||||
ex);
|
ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage storage = getStorage(mapConfig.getStorage());
|
Storage storage = getOrLoadStorage(mapConfig.getStorage());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
Logger.global.logInfo("Loading map '" + name + "'...");
|
Logger.global.logInfo("Loading map '" + id + "'...");
|
||||||
BmMap map = new BmMap(
|
BmMap map = new BmMap(
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
worldId,
|
|
||||||
world,
|
world,
|
||||||
storage,
|
storage,
|
||||||
getResourcePack(),
|
getOrLoadResourcePack(),
|
||||||
mapConfig
|
mapConfig
|
||||||
);
|
);
|
||||||
maps.put(id, map);
|
maps.put(id, map);
|
||||||
@ -303,12 +276,12 @@ private synchronized void loadMapConfig(String id, MapConfig mapConfig) throws C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Storage getStorage(String storageId) throws ConfigurationException {
|
public synchronized Storage getOrLoadStorage(String storageId) throws ConfigurationException, InterruptedException {
|
||||||
Storage storage = storages.get(storageId);
|
Storage storage = storages.get(storageId);
|
||||||
|
|
||||||
if (storage == null) {
|
if (storage == null) {
|
||||||
try {
|
try {
|
||||||
StorageConfig storageConfig = getConfigs().getStorageConfigs().get(storageId);
|
StorageConfig storageConfig = getConfig().getStorageConfigs().get(storageId);
|
||||||
if (storageConfig == null) {
|
if (storageConfig == null) {
|
||||||
throw new ConfigurationException("There is no storage-configuration for '" + storageId + "'!\n" +
|
throw new ConfigurationException("There is no storage-configuration for '" + storageId + "'!\n" +
|
||||||
"You will either need to define that storage, or change the map-config to use a storage-config that exists.");
|
"You will either need to define that storage, or change the map-config to use a storage-config that exists.");
|
||||||
@ -341,14 +314,18 @@ public synchronized Storage getStorage(String storageId) throws ConfigurationExc
|
|||||||
return storage;
|
return storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized ResourcePack getResourcePack() throws ConfigurationException, InterruptedException {
|
public @Nullable ResourcePack getResourcePack() {
|
||||||
|
return resourcePack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ResourcePack getOrLoadResourcePack() throws ConfigurationException, InterruptedException {
|
||||||
if (resourcePack == null) {
|
if (resourcePack == null) {
|
||||||
MinecraftVersion minecraftVersion = serverInterface.getMinecraftVersion();
|
MinecraftVersion minecraftVersion = config.getMinecraftVersion();
|
||||||
|
@Nullable Path resourcePackFolder = config.getResourcePacksFolder();
|
||||||
|
@Nullable Path modsFolder = config.getModsFolder();
|
||||||
|
|
||||||
Path defaultResourceFile = configs.getCoreConfig().getData().resolve("minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar");
|
Path defaultResourceFile = config.getCoreConfig().getData().resolve("minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar");
|
||||||
Path resourceExtensionsFile = configs.getCoreConfig().getData().resolve("resourceExtensions.zip");
|
Path resourceExtensionsFile = config.getCoreConfig().getData().resolve("resourceExtensions.zip");
|
||||||
|
|
||||||
Path resourcePackFolder = serverInterface.getConfigFolder().resolve("resourcepacks");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FileHelper.createDirectories(resourcePackFolder);
|
FileHelper.createDirectories(resourcePackFolder);
|
||||||
@ -360,8 +337,10 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
|
|||||||
ex);
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
if (!Files.exists(defaultResourceFile)) {
|
if (!Files.exists(defaultResourceFile)) {
|
||||||
if (configs.getCoreConfig().isAcceptDownload()) {
|
if (config.getCoreConfig().isAcceptDownload()) {
|
||||||
//download file
|
//download file
|
||||||
try {
|
try {
|
||||||
Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ...");
|
Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ...");
|
||||||
@ -380,6 +359,8 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(resourceExtensionsFile);
|
Files.deleteIfExists(resourceExtensionsFile);
|
||||||
FileHelper.createDirectories(resourceExtensionsFile.getParent());
|
FileHelper.createDirectories(resourceExtensionsFile.getParent());
|
||||||
@ -396,21 +377,25 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
|
|||||||
ex);
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
resourcePack = new ResourcePack();
|
ResourcePack resourcePack = new ResourcePack();
|
||||||
|
|
||||||
List<Path> resourcePackRoots = new ArrayList<>();
|
List<Path> resourcePackRoots = new ArrayList<>();
|
||||||
|
|
||||||
|
if (resourcePackFolder != null) {
|
||||||
// load from resourcepack folder
|
// load from resourcepack folder
|
||||||
try (Stream<Path> resourcepackFiles = Files.list(resourcePackFolder)) {
|
try (Stream<Path> resourcepackFiles = Files.list(resourcePackFolder)) {
|
||||||
resourcepackFiles
|
resourcepackFiles
|
||||||
.sorted(Comparator.reverseOrder())
|
.sorted(Comparator.reverseOrder())
|
||||||
.forEach(resourcePackRoots::add);
|
.forEach(resourcePackRoots::add);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (configs.getCoreConfig().isScanForModResources()) {
|
if (config.getCoreConfig().isScanForModResources()) {
|
||||||
|
|
||||||
// load from mods folder
|
// load from mods folder
|
||||||
Path modsFolder = serverInterface.getModsFolder().orElse(null);
|
|
||||||
if (modsFolder != null && Files.isDirectory(modsFolder)) {
|
if (modsFolder != null && Files.isDirectory(modsFolder)) {
|
||||||
try (Stream<Path> resourcepackFiles = Files.list(modsFolder)) {
|
try (Stream<Path> resourcepackFiles = Files.list(modsFolder)) {
|
||||||
resourcepackFiles
|
resourcepackFiles
|
||||||
@ -436,23 +421,20 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
|
|||||||
resourcePackRoots.add(defaultResourceFile);
|
resourcePackRoots.add(defaultResourceFile);
|
||||||
|
|
||||||
resourcePack.loadResources(resourcePackRoots);
|
resourcePack.loadResources(resourcePackRoots);
|
||||||
|
|
||||||
|
this.resourcePack = resourcePack;
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resourcePack;
|
return this.resourcePack;
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<ResourcePack> getResourcePackIfLoaded() {
|
|
||||||
return Optional.ofNullable(this.resourcePack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<Path> getWorldFolders() {
|
private Collection<Path> getWorldFolders() {
|
||||||
Set<Path> folders = new HashSet<>();
|
Set<Path> folders = new HashSet<>();
|
||||||
for (MapConfig mapConfig : configs.getMapConfigs().values()) {
|
for (MapConfig mapConfig : config.getMapConfigs().values()) {
|
||||||
Path folder = mapConfig.getWorld();
|
Path folder = mapConfig.getWorld();
|
||||||
if (folder == null) continue;
|
if (folder == null) continue;
|
||||||
folder = folder.toAbsolutePath().normalize();
|
folder = folder.toAbsolutePath().normalize();
|
||||||
@ -463,8 +445,8 @@ private Collection<Path> getWorldFolders() {
|
|||||||
return folders;
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlueMapConfigProvider getConfigs() {
|
public BlueMapConfiguration getConfig() {
|
||||||
return configs;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -24,6 +24,9 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common;
|
package de.bluecolored.bluemap.common;
|
||||||
|
|
||||||
|
import com.google.gson.FieldNamingPolicy;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
import de.bluecolored.bluemap.common.config.WebappConfig;
|
import de.bluecolored.bluemap.common.config.WebappConfig;
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
import de.bluecolored.bluemap.core.BlueMap;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
@ -47,6 +50,11 @@
|
|||||||
|
|
||||||
public class WebFilesManager {
|
public class WebFilesManager {
|
||||||
|
|
||||||
|
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||||
|
//.setPrettyPrinting() // enable pretty printing for easy editing
|
||||||
|
.create();
|
||||||
|
|
||||||
private final Path webRoot;
|
private final Path webRoot;
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
|
|
||||||
@ -61,7 +69,7 @@ public Path getSettingsFile() {
|
|||||||
|
|
||||||
public void loadSettings() throws IOException {
|
public void loadSettings() throws IOException {
|
||||||
try (BufferedReader reader = Files.newBufferedReader(getSettingsFile())) {
|
try (BufferedReader reader = Files.newBufferedReader(getSettingsFile())) {
|
||||||
this.settings = ResourcesGson.INSTANCE.fromJson(reader, Settings.class);
|
this.settings = GSON.fromJson(reader, Settings.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +77,7 @@ public void saveSettings() throws IOException {
|
|||||||
FileHelper.createDirectories(getSettingsFile().getParent());
|
FileHelper.createDirectories(getSettingsFile().getParent());
|
||||||
try (BufferedWriter writer = Files.newBufferedWriter(getSettingsFile(),
|
try (BufferedWriter writer = Files.newBufferedWriter(getSettingsFile(),
|
||||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
ResourcesGson.INSTANCE.toJson(this.settings, writer);
|
GSON.toJson(this.settings, writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,13 +30,15 @@
|
|||||||
import de.bluecolored.bluemap.api.BlueMapMap;
|
import de.bluecolored.bluemap.api.BlueMapMap;
|
||||||
import de.bluecolored.bluemap.api.BlueMapWorld;
|
import de.bluecolored.bluemap.api.BlueMapWorld;
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||||
|
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
import de.bluecolored.bluemap.core.BlueMap;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.Collection;
|
||||||
import java.util.*;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -75,30 +77,21 @@ public de.bluecolored.bluemap.api.plugin.Plugin getPlugin() {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<BlueMapMap> getMaps() {
|
public Collection<BlueMapMap> getMaps() {
|
||||||
Map<String, BmMap> maps = plugin.getMaps();
|
Map<String, BmMap> maps = plugin.getBlueMap().getMaps();
|
||||||
if (maps == null) return Collections.emptyList();
|
return maps.keySet().stream()
|
||||||
|
.map(this::getMap)
|
||||||
return maps.values().stream()
|
.filter(Optional::isPresent)
|
||||||
.map(map -> {
|
.map(Optional::get)
|
||||||
try {
|
|
||||||
return new BlueMapMapImpl(plugin, map);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.global.logError("[API] Failed to create BlueMapMap for map " + map.getId(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toUnmodifiableSet());
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<BlueMapWorld> getWorlds() {
|
public Collection<BlueMapWorld> getWorlds() {
|
||||||
Map<String, World> worlds = plugin.getWorlds();
|
Map<String, World> worlds = plugin.getBlueMap().getWorlds();
|
||||||
if (worlds == null) return Collections.emptyList();
|
return worlds.keySet().stream()
|
||||||
|
.map(this::getWorld)
|
||||||
return worlds.values().stream()
|
.filter(Optional::isPresent)
|
||||||
.map(world -> getWorld(world).orElse(null))
|
.map(Optional::get)
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toUnmodifiableSet());
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,43 +101,24 @@ public Optional<BlueMapWorld> getWorld(Object world) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Optional<BlueMapWorld> getWorldUncached(Object world) {
|
public Optional<BlueMapWorld> getWorldUncached(Object world) {
|
||||||
var worlds = plugin.getWorlds();
|
|
||||||
if (worlds == null) return Optional.empty();
|
|
||||||
|
|
||||||
if (world instanceof UUID) {
|
|
||||||
var coreWorld = worlds.get(world.toString());
|
|
||||||
if (coreWorld != null) world = coreWorld;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (world instanceof String) {
|
if (world instanceof String) {
|
||||||
var coreWorld = worlds.get(world);
|
var coreWorld = plugin.getBlueMap().getWorlds().get(world);
|
||||||
if (coreWorld != null) world = coreWorld;
|
if (coreWorld != null) world = coreWorld;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (world instanceof World) {
|
if (world instanceof World) {
|
||||||
var coreWorld = (World) world;
|
var coreWorld = (World) world;
|
||||||
try {
|
|
||||||
return Optional.of(new BlueMapWorldImpl(plugin, coreWorld));
|
return Optional.of(new BlueMapWorldImpl(plugin, coreWorld));
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.global.logError("[API] Failed to create BlueMapWorld for world " + coreWorld.getSaveFolder(), e);
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverWorld = plugin.getServerInterface().getWorld(world).orElse(null);
|
ServerWorld serverWorld = plugin.getServerInterface().getServerWorld(world).orElse(null);
|
||||||
if (serverWorld == null) return Optional.empty();
|
if (serverWorld == null) return Optional.empty();
|
||||||
|
|
||||||
try {
|
World coreWorld = plugin.getWorld(serverWorld);
|
||||||
String id = plugin.getBlueMap().getWorldId(serverWorld.getSaveFolder());
|
|
||||||
var coreWorld = worlds.get(id);
|
|
||||||
if (coreWorld == null) return Optional.empty();
|
if (coreWorld == null) return Optional.empty();
|
||||||
|
|
||||||
return Optional.of(new BlueMapWorldImpl(plugin, coreWorld));
|
return Optional.of(new BlueMapWorldImpl(plugin, coreWorld));
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.global.logError("[API] Failed to create BlueMapWorld for world " + serverWorld.getSaveFolder(), e);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -153,8 +127,7 @@ public Optional<BlueMapMap> getMap(String id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Optional<BlueMapMap> getMapUncached(String id) {
|
public Optional<BlueMapMap> getMapUncached(String id) {
|
||||||
var maps = plugin.getMaps();
|
var maps = plugin.getBlueMap().getMaps();
|
||||||
if (maps == null) return Optional.empty();
|
|
||||||
|
|
||||||
var map = maps.get(id);
|
var map = maps.get(id);
|
||||||
if (map == null) return Optional.empty();
|
if (map == null) return Optional.empty();
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
import de.bluecolored.bluemap.api.BlueMapWorld;
|
import de.bluecolored.bluemap.api.BlueMapWorld;
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -38,13 +38,13 @@
|
|||||||
|
|
||||||
public class BlueMapWorldImpl implements BlueMapWorld {
|
public class BlueMapWorldImpl implements BlueMapWorld {
|
||||||
|
|
||||||
private final WeakReference<Plugin> plugin;
|
|
||||||
private final String id;
|
private final String id;
|
||||||
|
private final WeakReference<Plugin> plugin;
|
||||||
private final WeakReference<World> world;
|
private final WeakReference<World> world;
|
||||||
|
|
||||||
public BlueMapWorldImpl(Plugin plugin, World world) throws IOException {
|
public BlueMapWorldImpl(Plugin plugin, World world) {
|
||||||
|
this.id = world.getId();
|
||||||
this.plugin = new WeakReference<>(plugin);
|
this.plugin = new WeakReference<>(plugin);
|
||||||
this.id = plugin.getBlueMap().getWorldId(world.getSaveFolder());
|
|
||||||
this.world = new WeakReference<>(world);
|
this.world = new WeakReference<>(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,15 +58,23 @@ public String getId() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public Path getSaveFolder() {
|
public Path getSaveFolder() {
|
||||||
return unpack(world).getSaveFolder();
|
World world = unpack(this.world);
|
||||||
|
if (world instanceof MCAWorld) {
|
||||||
|
return ((MCAWorld) world).getDimensionFolder();
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("This world-type has no save-folder.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<BlueMapMap> getMaps() {
|
public Collection<BlueMapMap> getMaps() {
|
||||||
return unpack(plugin).getMaps().values().stream()
|
Plugin plugin = unpack(this.plugin);
|
||||||
.filter(map -> map.getWorld().equals(unpack(world)))
|
World world = unpack(this.world);
|
||||||
.map(map -> new BlueMapMapImpl(unpack(plugin), map, this))
|
return plugin.getBlueMap().getMaps().values().stream()
|
||||||
|
.filter(map -> map.getWorld().equals(world))
|
||||||
|
.map(map -> new BlueMapMapImpl(plugin, map, this))
|
||||||
.collect(Collectors.toUnmodifiableSet());
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ public boolean isRunning() {
|
|||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
if (!isRunning()){
|
if (!isRunning()){
|
||||||
renderManager.start(plugin.getConfigs().getCoreConfig().getRenderThreadCount());
|
renderManager.start(plugin.getBlueMap().getConfig().getCoreConfig().getRenderThreadCount());
|
||||||
}
|
}
|
||||||
plugin.getPluginState().setRenderThreadsEnabled(true);
|
plugin.getPluginState().setRenderThreadsEnabled(true);
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ public WebAppImpl(Plugin plugin) {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Path getWebRoot() {
|
public Path getWebRoot() {
|
||||||
return plugin.getConfigs().getWebappConfig().getWebroot();
|
return plugin.getBlueMap().getConfig().getWebappConfig().getWebroot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -98,6 +98,7 @@ public String createImage(BufferedImage image, String path) throws IOException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
public Map<String, String> availableImages() throws IOException {
|
public Map<String, String> availableImages() throws IOException {
|
||||||
Path webRoot = getWebRoot().toAbsolutePath();
|
Path webRoot = getWebRoot().toAbsolutePath();
|
||||||
String separator = webRoot.getFileSystem().getSeparator();
|
String separator = webRoot.getFileSystem().getSeparator();
|
||||||
|
@ -25,114 +25,92 @@
|
|||||||
package de.bluecolored.bluemap.common.config;
|
package de.bluecolored.bluemap.common.config;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.common.BlueMapConfigProvider;
|
import de.bluecolored.bluemap.common.BlueMapConfiguration;
|
||||||
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
import de.bluecolored.bluemap.core.BlueMap;
|
||||||
|
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
import de.bluecolored.bluemap.core.resources.datapack.DataPack;
|
||||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||||
import de.bluecolored.bluemap.core.util.Tristate;
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@DebugDump
|
@DebugDump
|
||||||
public class BlueMapConfigs implements BlueMapConfigProvider {
|
@Getter
|
||||||
|
public class BlueMapConfigManager implements BlueMapConfiguration {
|
||||||
|
|
||||||
private final ServerInterface serverInterface;
|
|
||||||
private final ConfigManager configManager;
|
private final ConfigManager configManager;
|
||||||
|
|
||||||
|
private final MinecraftVersion minecraftVersion;
|
||||||
private final CoreConfig coreConfig;
|
private final CoreConfig coreConfig;
|
||||||
private final WebserverConfig webserverConfig;
|
private final WebserverConfig webserverConfig;
|
||||||
private final WebappConfig webappConfig;
|
private final WebappConfig webappConfig;
|
||||||
private final PluginConfig pluginConfig;
|
private final PluginConfig pluginConfig;
|
||||||
private final Map<String, MapConfig> mapConfigs;
|
private final Map<String, MapConfig> mapConfigs;
|
||||||
private final Map<String, StorageConfig> storageConfigs;
|
private final Map<String, StorageConfig> storageConfigs;
|
||||||
|
private final Path resourcePacksFolder;
|
||||||
|
private final @Nullable Path modsFolder;
|
||||||
|
|
||||||
public BlueMapConfigs(ServerInterface serverInterface) throws ConfigurationException {
|
@Builder
|
||||||
this(serverInterface, Path.of("bluemap"), Path.of("bluemap", "web"), true);
|
private BlueMapConfigManager(
|
||||||
}
|
@NonNull MinecraftVersion minecraftVersion,
|
||||||
|
@NonNull Path configRoot,
|
||||||
|
@Nullable Path defaultDataFolder,
|
||||||
|
@Nullable Path defaultWebroot,
|
||||||
|
@Nullable Collection<ServerWorld> autoConfigWorlds,
|
||||||
|
@Nullable Boolean usePluginConfig,
|
||||||
|
@Nullable Boolean useMetricsConfig,
|
||||||
|
@Nullable Path resourcePacksFolder,
|
||||||
|
@Nullable Path modsFolder
|
||||||
|
) throws ConfigurationException {
|
||||||
|
// set defaults
|
||||||
|
if (defaultDataFolder == null) defaultDataFolder = Path.of("bluemap");
|
||||||
|
if (defaultWebroot == null) defaultWebroot = Path.of("bluemap", "web");
|
||||||
|
if (autoConfigWorlds == null) autoConfigWorlds = Collections.emptyList();
|
||||||
|
if (usePluginConfig == null) usePluginConfig = true;
|
||||||
|
if (useMetricsConfig == null) useMetricsConfig = true;
|
||||||
|
if (resourcePacksFolder == null) resourcePacksFolder = configRoot.resolve("resourcepacks");
|
||||||
|
|
||||||
public BlueMapConfigs(ServerInterface serverInterface, Path defaultDataFolder, Path defaultWebroot, boolean usePluginConf) throws ConfigurationException {
|
// load
|
||||||
this.serverInterface = serverInterface;
|
this.minecraftVersion = minecraftVersion;
|
||||||
this.configManager = new ConfigManager(serverInterface.getConfigFolder());
|
this.configManager = new ConfigManager(configRoot);
|
||||||
|
this.coreConfig = loadCoreConfig(defaultDataFolder, useMetricsConfig);
|
||||||
this.coreConfig = loadCoreConfig(defaultDataFolder);
|
|
||||||
this.webappConfig = loadWebappConfig(defaultWebroot);
|
this.webappConfig = loadWebappConfig(defaultWebroot);
|
||||||
this.webserverConfig = loadWebserverConfig(webappConfig.getWebroot(), coreConfig.getData());
|
this.webserverConfig = loadWebserverConfig(webappConfig.getWebroot(), coreConfig.getData());
|
||||||
this.pluginConfig = usePluginConf ? loadPluginConfig() : new PluginConfig();
|
this.pluginConfig = usePluginConfig ? loadPluginConfig() : new PluginConfig();
|
||||||
this.storageConfigs = Collections.unmodifiableMap(loadStorageConfigs(webappConfig.getWebroot()));
|
this.storageConfigs = Collections.unmodifiableMap(loadStorageConfigs(webappConfig.getWebroot()));
|
||||||
this.mapConfigs = Collections.unmodifiableMap(loadMapConfigs());
|
this.mapConfigs = Collections.unmodifiableMap(loadMapConfigs(autoConfigWorlds));
|
||||||
|
this.resourcePacksFolder = resourcePacksFolder;
|
||||||
|
this.modsFolder = modsFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigManager getConfigManager() {
|
private CoreConfig loadCoreConfig(Path defaultDataFolder, boolean useMetricsConfig) throws ConfigurationException {
|
||||||
return configManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CoreConfig getCoreConfig() {
|
|
||||||
return coreConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WebappConfig getWebappConfig() {
|
|
||||||
return webappConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WebserverConfig getWebserverConfig() {
|
|
||||||
return webserverConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PluginConfig getPluginConfig() {
|
|
||||||
return pluginConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, MapConfig> getMapConfigs() {
|
|
||||||
return mapConfigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, StorageConfig> getStorageConfigs() {
|
|
||||||
return storageConfigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized CoreConfig loadCoreConfig(Path defaultDataFolder) throws ConfigurationException {
|
|
||||||
Path configFileRaw = Path.of("core");
|
Path configFileRaw = Path.of("core");
|
||||||
Path configFile = configManager.findConfigPath(configFileRaw);
|
Path configFile = configManager.findConfigPath(configFileRaw);
|
||||||
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 {
|
||||||
FileHelper.createDirectories(configFolder);
|
FileHelper.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)
|
.setConditional("metrics", useMetricsConfig)
|
||||||
.setVariable("timestamp", LocalDateTime.now().withNano(0).toString())
|
.setVariable("timestamp", LocalDateTime.now().withNano(0).toString())
|
||||||
.setVariable("version", BlueMap.VERSION)
|
.setVariable("version", BlueMap.VERSION)
|
||||||
.setVariable("data", formatPath(defaultDataFolder))
|
.setVariable("data", formatPath(defaultDataFolder))
|
||||||
.setVariable("implementation", "bukkit")
|
.setVariable("implementation", "bukkit")
|
||||||
.setVariable("render-thread-count", Integer.toString(presetRenderThreadCount))
|
.setVariable("render-thread-count", Integer.toString(suggestRenderThreadCount()))
|
||||||
.setVariable("logfile", formatPath(defaultDataFolder.resolve("logs").resolve("debug.log")))
|
.setVariable("logfile", formatPath(defaultDataFolder.resolve("logs").resolve("debug.log")))
|
||||||
.setVariable("logfile-with-time", formatPath(defaultDataFolder.resolve("logs").resolve("debug_%1$tF_%1$tT.log")))
|
.setVariable("logfile-with-time", formatPath(defaultDataFolder.resolve("logs").resolve("debug_%1$tF_%1$tT.log")))
|
||||||
.build(),
|
.build(),
|
||||||
@ -146,7 +124,22 @@ private synchronized CoreConfig loadCoreConfig(Path defaultDataFolder) throws Co
|
|||||||
return configManager.loadConfig(configFileRaw, CoreConfig.class);
|
return configManager.loadConfig(configFileRaw, CoreConfig.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized WebserverConfig loadWebserverConfig(Path defaultWebroot, Path dataRoot) throws ConfigurationException {
|
/**
|
||||||
|
* determine render-thread preset (very pessimistic, rather let people increase it themselves)
|
||||||
|
*/
|
||||||
|
private int suggestRenderThreadCount() {
|
||||||
|
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;
|
||||||
|
return presetRenderThreadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebserverConfig loadWebserverConfig(Path defaultWebroot, Path dataRoot) throws ConfigurationException {
|
||||||
Path configFileRaw = Path.of("webserver");
|
Path configFileRaw = Path.of("webserver");
|
||||||
Path configFile = configManager.findConfigPath(configFileRaw);
|
Path configFile = configManager.findConfigPath(configFileRaw);
|
||||||
Path configFolder = configFile.getParent();
|
Path configFolder = configFile.getParent();
|
||||||
@ -171,7 +164,7 @@ private synchronized WebserverConfig loadWebserverConfig(Path defaultWebroot, Pa
|
|||||||
return configManager.loadConfig(configFileRaw, WebserverConfig.class);
|
return configManager.loadConfig(configFileRaw, WebserverConfig.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized WebappConfig loadWebappConfig(Path defaultWebroot) throws ConfigurationException {
|
private WebappConfig loadWebappConfig(Path defaultWebroot) throws ConfigurationException {
|
||||||
Path configFileRaw = Path.of("webapp");
|
Path configFileRaw = Path.of("webapp");
|
||||||
Path configFile = configManager.findConfigPath(configFileRaw);
|
Path configFile = configManager.findConfigPath(configFileRaw);
|
||||||
Path configFolder = configFile.getParent();
|
Path configFolder = configFile.getParent();
|
||||||
@ -194,7 +187,7 @@ private synchronized WebappConfig loadWebappConfig(Path defaultWebroot) throws C
|
|||||||
return configManager.loadConfig(configFileRaw, WebappConfig.class);
|
return configManager.loadConfig(configFileRaw, WebappConfig.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized PluginConfig loadPluginConfig() throws ConfigurationException {
|
private PluginConfig loadPluginConfig() throws ConfigurationException {
|
||||||
Path configFileRaw = Path.of("plugin");
|
Path configFileRaw = Path.of("plugin");
|
||||||
Path configFile = configManager.findConfigPath(configFileRaw);
|
Path configFile = configManager.findConfigPath(configFileRaw);
|
||||||
Path configFolder = configFile.getParent();
|
Path configFolder = configFile.getParent();
|
||||||
@ -216,7 +209,7 @@ private synchronized PluginConfig loadPluginConfig() throws ConfigurationExcepti
|
|||||||
return configManager.loadConfig(configFileRaw, PluginConfig.class);
|
return configManager.loadConfig(configFileRaw, PluginConfig.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized Map<String, MapConfig> loadMapConfigs() throws ConfigurationException {
|
private Map<String, MapConfig> loadMapConfigs(Collection<ServerWorld> autoConfigWorlds) throws ConfigurationException {
|
||||||
Map<String, MapConfig> mapConfigs = new HashMap<>();
|
Map<String, MapConfig> mapConfigs = new HashMap<>();
|
||||||
|
|
||||||
Path mapFolder = Paths.get("maps");
|
Path mapFolder = Paths.get("maps");
|
||||||
@ -225,41 +218,68 @@ private synchronized Map<String, MapConfig> loadMapConfigs() throws Configuratio
|
|||||||
if (!Files.exists(mapConfigFolder)){
|
if (!Files.exists(mapConfigFolder)){
|
||||||
try {
|
try {
|
||||||
FileHelper.createDirectories(mapConfigFolder);
|
FileHelper.createDirectories(mapConfigFolder);
|
||||||
var worlds = serverInterface.getLoadedWorlds();
|
if (autoConfigWorlds.isEmpty()) {
|
||||||
if (worlds.isEmpty()) {
|
Path worldFolder = Path.of("world");
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
mapConfigFolder.resolve("overworld.conf"),
|
mapConfigFolder.resolve("overworld.conf"),
|
||||||
createOverworldMapTemplate("Overworld", Path.of("world"), 0).build(),
|
createOverworldMapTemplate("Overworld", worldFolder,
|
||||||
|
DataPack.DIMENSION_OVERWORLD, 0).build(),
|
||||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||||
);
|
);
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
mapConfigFolder.resolve("nether.conf"),
|
mapConfigFolder.resolve("nether.conf"),
|
||||||
createNetherMapTemplate("Nether", Path.of("world", "DIM-1"), 0).build(),
|
createNetherMapTemplate("Nether", worldFolder,
|
||||||
|
DataPack.DIMENSION_THE_NETHER, 0).build(),
|
||||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||||
);
|
);
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
mapConfigFolder.resolve("end.conf"),
|
mapConfigFolder.resolve("end.conf"),
|
||||||
createEndMapTemplate("End", Path.of("world", "DIM1"), 0).build(),
|
createEndMapTemplate("End", worldFolder,
|
||||||
|
DataPack.DIMENSION_THE_END, 0).build(),
|
||||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
for (var world : worlds) {
|
// make sure overworld-dimensions come first, so they are the ones where the
|
||||||
String name = world.getName().orElse(world.getDimension().getName());
|
// dimension-key is omitted in the generated map-id
|
||||||
Path worldFolder = world.getSaveFolder();
|
List<ServerWorld> overworldFirstAutoConfigWorlds = new ArrayList<>(autoConfigWorlds.size());
|
||||||
|
overworldFirstAutoConfigWorlds.addAll(autoConfigWorlds);
|
||||||
|
overworldFirstAutoConfigWorlds.sort(Comparator.comparingInt(w ->
|
||||||
|
DataPack.DIMENSION_OVERWORLD.equals(w.getDimension()) ? 0 : 1
|
||||||
|
));
|
||||||
|
|
||||||
Path configFile = mapConfigFolder.resolve(sanitiseMapId(name.toLowerCase(Locale.ROOT)) + ".conf");
|
Set<String> mapIds = new HashSet<>();
|
||||||
|
for (var world : overworldFirstAutoConfigWorlds) {
|
||||||
|
Path worldFolder = world.getWorldFolder().normalize();
|
||||||
|
Key dimension = world.getDimension();
|
||||||
|
|
||||||
|
String dimensionName = dimension.getNamespace().equals("minecraft") ?
|
||||||
|
dimension.getValue() : dimension.getFormatted();
|
||||||
|
|
||||||
|
// find unique map id
|
||||||
|
String id = sanitiseMapId(worldFolder.getFileName().toString()).toLowerCase(Locale.ROOT);
|
||||||
|
if (mapIds.contains(id))
|
||||||
|
id = sanitiseMapId(worldFolder.getFileName() + "_" + dimensionName).toLowerCase(Locale.ROOT);
|
||||||
int i = 1;
|
int i = 1;
|
||||||
while (Files.exists(configFile)) {
|
String uniqueId = id;
|
||||||
configFile = mapConfigFolder.resolve(sanitiseMapId(name.toLowerCase(Locale.ROOT)) + '_' + (++i) + ".conf");
|
while (mapIds.contains(uniqueId))
|
||||||
}
|
uniqueId = id + "_" + (++i);
|
||||||
|
mapIds.add(uniqueId);
|
||||||
|
|
||||||
if (i > 1) name = name + " " + i;
|
Path configFile = mapConfigFolder.resolve(uniqueId + ".conf");
|
||||||
|
String name = worldFolder.getFileName() + " (" + dimensionName + ")";
|
||||||
|
if (i > 1) name = name + " (" + i + ")";
|
||||||
|
|
||||||
ConfigTemplate template;
|
ConfigTemplate template;
|
||||||
switch (world.getDimension()) {
|
switch (world.getDimension().getFormatted()) {
|
||||||
case NETHER: template = createNetherMapTemplate(name, worldFolder, i - 1); break;
|
case "minecraft:the_nether":
|
||||||
case END: template = createEndMapTemplate(name, worldFolder, i - 1); break;
|
template = createNetherMapTemplate(name, worldFolder, dimension, i - 1);
|
||||||
default: template = createOverworldMapTemplate(name, worldFolder, i - 1); break;
|
break;
|
||||||
|
case "minecraft:the_end":
|
||||||
|
template = createEndMapTemplate(name, worldFolder, dimension, i - 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
template = createOverworldMapTemplate(name, worldFolder, dimension, i - 1);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
@ -302,7 +322,7 @@ private synchronized Map<String, MapConfig> loadMapConfigs() throws Configuratio
|
|||||||
return mapConfigs;
|
return mapConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized Map<String, StorageConfig> loadStorageConfigs(Path defaultWebroot) throws ConfigurationException {
|
private Map<String, StorageConfig> loadStorageConfigs(Path defaultWebroot) throws ConfigurationException {
|
||||||
Map<String, StorageConfig> storageConfigs = new HashMap<>();
|
Map<String, StorageConfig> storageConfigs = new HashMap<>();
|
||||||
|
|
||||||
Path storageFolder = Paths.get("storages");
|
Path storageFolder = Paths.get("storages");
|
||||||
@ -357,43 +377,43 @@ private String sanitiseMapId(String id) {
|
|||||||
return id.replaceAll("\\W", "_");
|
return id.replaceAll("\\W", "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigTemplate createOverworldMapTemplate(String name, Path worldFolder, int index) throws IOException {
|
private ConfigTemplate createOverworldMapTemplate(String name, Path worldFolder, Key dimension, int index) throws IOException {
|
||||||
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
||||||
.setVariable("name", name)
|
.setVariable("name", name)
|
||||||
.setVariable("sorting", "" + index)
|
.setVariable("sorting", "" + index)
|
||||||
.setVariable("world", formatPath(worldFolder))
|
.setVariable("world", formatPath(worldFolder))
|
||||||
|
.setVariable("dimension", dimension.getFormatted())
|
||||||
.setVariable("sky-color", "#7dabff")
|
.setVariable("sky-color", "#7dabff")
|
||||||
.setVariable("void-color", "#000000")
|
.setVariable("void-color", "#000000")
|
||||||
.setVariable("ambient-light", "0.1")
|
.setVariable("ambient-light", "0.1")
|
||||||
.setVariable("world-sky-light", "15")
|
|
||||||
.setVariable("remove-caves-below-y", "55")
|
.setVariable("remove-caves-below-y", "55")
|
||||||
.setConditional("max-y-comment", true)
|
.setConditional("max-y-comment", true)
|
||||||
.setVariable("max-y", "100");
|
.setVariable("max-y", "100");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigTemplate createNetherMapTemplate(String name, Path worldFolder, int index) throws IOException {
|
private ConfigTemplate createNetherMapTemplate(String name, Path worldFolder, Key dimension, int index) throws IOException {
|
||||||
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
||||||
.setVariable("name", name)
|
.setVariable("name", name)
|
||||||
.setVariable("sorting", "" + (100 + index))
|
.setVariable("sorting", "" + (100 + index))
|
||||||
.setVariable("world", formatPath(worldFolder))
|
.setVariable("world", formatPath(worldFolder))
|
||||||
|
.setVariable("dimension", dimension.getFormatted())
|
||||||
.setVariable("sky-color", "#290000")
|
.setVariable("sky-color", "#290000")
|
||||||
.setVariable("void-color", "#150000")
|
.setVariable("void-color", "#150000")
|
||||||
.setVariable("ambient-light", "0.6")
|
.setVariable("ambient-light", "0.6")
|
||||||
.setVariable("world-sky-light", "0")
|
|
||||||
.setVariable("remove-caves-below-y", "-10000")
|
.setVariable("remove-caves-below-y", "-10000")
|
||||||
.setConditional("max-y-comment", false)
|
.setConditional("max-y-comment", false)
|
||||||
.setVariable("max-y", "90");
|
.setVariable("max-y", "90");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigTemplate createEndMapTemplate(String name, Path worldFolder, int index) throws IOException {
|
private ConfigTemplate createEndMapTemplate(String name, Path worldFolder, Key dimension, int index) throws IOException {
|
||||||
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
||||||
.setVariable("name", name)
|
.setVariable("name", name)
|
||||||
.setVariable("sorting", "" + (200 + index))
|
.setVariable("sorting", "" + (200 + index))
|
||||||
.setVariable("world", formatPath(worldFolder))
|
.setVariable("world", formatPath(worldFolder))
|
||||||
|
.setVariable("dimension", dimension.getFormatted())
|
||||||
.setVariable("sky-color", "#080010")
|
.setVariable("sky-color", "#080010")
|
||||||
.setVariable("void-color", "#080010")
|
.setVariable("void-color", "#080010")
|
||||||
.setVariable("ambient-light", "0.6")
|
.setVariable("ambient-light", "0.6")
|
||||||
.setVariable("world-sky-light", "0")
|
|
||||||
.setVariable("remove-caves-below-y", "-10000")
|
.setVariable("remove-caves-below-y", "-10000")
|
||||||
.setConditional("max-y-comment", true)
|
.setConditional("max-y-comment", true)
|
||||||
.setVariable("max-y", "100");
|
.setVariable("max-y", "100");
|
@ -25,8 +25,10 @@
|
|||||||
package de.bluecolored.bluemap.common.config;
|
package de.bluecolored.bluemap.common.config;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
|
import de.bluecolored.bluemap.common.config.typeserializer.KeyTypeSerializer;
|
||||||
import de.bluecolored.bluemap.common.config.typeserializer.Vector2iTypeSerializer;
|
import de.bluecolored.bluemap.common.config.typeserializer.Vector2iTypeSerializer;
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
import de.bluecolored.bluemap.core.BlueMap;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.spongepowered.configurate.ConfigurateException;
|
import org.spongepowered.configurate.ConfigurateException;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
@ -165,6 +167,7 @@ private ConfigurationLoader<? extends ConfigurationNode> getLoader(Path path){
|
|||||||
.path(path)
|
.path(path)
|
||||||
.defaultOptions(o -> o.serializers(b -> {
|
.defaultOptions(o -> o.serializers(b -> {
|
||||||
b.register(Vector2i.class, new Vector2iTypeSerializer());
|
b.register(Vector2i.class, new Vector2iTypeSerializer());
|
||||||
|
b.register(Key.class, new KeyTypeSerializer());
|
||||||
}))
|
}))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -28,43 +28,45 @@
|
|||||||
import com.flowpowered.math.vector.Vector3i;
|
import com.flowpowered.math.vector.Vector3i;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.map.MapSettings;
|
import de.bluecolored.bluemap.core.map.MapSettings;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||||
@DebugDump
|
@DebugDump
|
||||||
@ConfigSerializable
|
@ConfigSerializable
|
||||||
|
@Getter
|
||||||
public class MapConfig implements MapSettings {
|
public class MapConfig implements MapSettings {
|
||||||
|
|
||||||
private String name = null;
|
@Nullable private Path world = null;
|
||||||
|
@Nullable private Key dimension = null;
|
||||||
|
|
||||||
private Path world = null;
|
@Nullable private String name = null;
|
||||||
|
|
||||||
private int sorting = 0;
|
private int sorting = 0;
|
||||||
|
|
||||||
private Vector2i startPos = null;
|
@Nullable private Vector2i startPos = null;
|
||||||
|
|
||||||
private String skyColor = "#7dabff";
|
private String skyColor = "#7dabff";
|
||||||
private String voidColor = "#000000";
|
private String voidColor = "#000000";
|
||||||
|
|
||||||
private float ambientLight = 0;
|
private float ambientLight = 0;
|
||||||
|
|
||||||
private int worldSkyLight = 15;
|
|
||||||
|
|
||||||
private int removeCavesBelowY = 55;
|
private int removeCavesBelowY = 55;
|
||||||
private int caveDetectionOceanFloor = 10000;
|
private int caveDetectionOceanFloor = 10000;
|
||||||
private boolean caveDetectionUsesBlockLight = false;
|
private boolean caveDetectionUsesBlockLight = false;
|
||||||
|
|
||||||
private int minX = Integer.MIN_VALUE;
|
@Getter(AccessLevel.NONE) private int minX = Integer.MIN_VALUE;
|
||||||
private int maxX = Integer.MAX_VALUE;
|
@Getter(AccessLevel.NONE) private int maxX = Integer.MAX_VALUE;
|
||||||
private int minZ = Integer.MIN_VALUE;
|
@Getter(AccessLevel.NONE) private int minZ = Integer.MIN_VALUE;
|
||||||
private int maxZ = Integer.MAX_VALUE;
|
@Getter(AccessLevel.NONE) private int maxZ = Integer.MAX_VALUE;
|
||||||
private int minY = Integer.MIN_VALUE;
|
@Getter(AccessLevel.NONE) private int minY = Integer.MIN_VALUE;
|
||||||
private int maxY = Integer.MAX_VALUE;
|
@Getter(AccessLevel.NONE) private int maxY = Integer.MAX_VALUE;
|
||||||
|
|
||||||
private transient Vector3i min = null;
|
private transient Vector3i min = null;
|
||||||
private transient Vector3i max = null;
|
private transient Vector3i max = null;
|
||||||
@ -80,7 +82,7 @@ public class MapConfig implements MapSettings {
|
|||||||
|
|
||||||
private boolean ignoreMissingLightData = false;
|
private boolean ignoreMissingLightData = false;
|
||||||
|
|
||||||
private ConfigurationNode markerSets = null;
|
@Nullable private ConfigurationNode markerSets = null;
|
||||||
|
|
||||||
// hidden config fields
|
// hidden config fields
|
||||||
private int hiresTileSize = 32;
|
private int hiresTileSize = 32;
|
||||||
@ -88,61 +90,6 @@ public class MapConfig implements MapSettings {
|
|||||||
private int lodCount = 3;
|
private int lodCount = 3;
|
||||||
private int lodFactor = 5;
|
private int lodFactor = 5;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Path getWorld() {
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSorting() {
|
|
||||||
return sorting;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Vector2i> getStartPos() {
|
|
||||||
return Optional.ofNullable(startPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSkyColor() {
|
|
||||||
return skyColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getVoidColor() {
|
|
||||||
return voidColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getAmbientLight() {
|
|
||||||
return ambientLight;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWorldSkyLight() {
|
|
||||||
return worldSkyLight;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getRemoveCavesBelowY() {
|
|
||||||
return removeCavesBelowY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCaveDetectionUsesBlockLight() {
|
|
||||||
return caveDetectionUsesBlockLight;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCaveDetectionOceanFloor() {
|
|
||||||
return caveDetectionOceanFloor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3i getMinPos() {
|
public Vector3i getMinPos() {
|
||||||
if (min == null) min = new Vector3i(minX, minY, minZ);
|
if (min == null) min = new Vector3i(minX, minY, minZ);
|
||||||
return min;
|
return min;
|
||||||
@ -153,57 +100,4 @@ public Vector3i getMaxPos() {
|
|||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMinInhabitedTime() {
|
|
||||||
return minInhabitedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinInhabitedTimeRadius() {
|
|
||||||
return minInhabitedTimeRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRenderEdges() {
|
|
||||||
return renderEdges;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSaveHiresLayer() {
|
|
||||||
return saveHiresLayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStorage() {
|
|
||||||
return storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIgnoreMissingLightData() {
|
|
||||||
return ignoreMissingLightData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public ConfigurationNode getMarkerSets() {
|
|
||||||
return markerSets;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHiresTileSize() {
|
|
||||||
return hiresTileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLowresTileSize() {
|
|
||||||
return lowresTileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLodCount() {
|
|
||||||
return lodCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLodFactor() {
|
|
||||||
return lodFactor;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,30 +22,27 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common.serverinterface;
|
package de.bluecolored.bluemap.common.config.typeserializer;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
import org.spongepowered.configurate.serialize.SerializationException;
|
||||||
|
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||||
|
|
||||||
public enum Dimension {
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
OVERWORLD ("Overworld", Path.of("")),
|
public class KeyTypeSerializer implements TypeSerializer<Key> {
|
||||||
NETHER ("Nether", Path.of("DIM-1")),
|
|
||||||
END ("End", Path.of("DIM1"));
|
|
||||||
|
|
||||||
private final String name;
|
@Override
|
||||||
private final Path dimensionSubPath;
|
public Key deserialize(Type type, ConfigurationNode node) {
|
||||||
|
String formatted = node.getString();
|
||||||
Dimension(String name, Path dimensionSubPath) {
|
return formatted != null ? new Key(node.getString()) : null;
|
||||||
this.name = name;
|
|
||||||
this.dimensionSubPath = dimensionSubPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
@Override
|
||||||
return name;
|
public void serialize(Type type, @Nullable Key obj, ConfigurationNode node) throws SerializationException {
|
||||||
}
|
if (obj != null) node.set(obj.getFormatted());
|
||||||
|
|
||||||
public Path getDimensionSubPath() {
|
|
||||||
return dimensionSubPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -27,9 +27,9 @@
|
|||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import de.bluecolored.bluemap.common.config.PluginConfig;
|
import de.bluecolored.bluemap.common.config.PluginConfig;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.Player;
|
import de.bluecolored.bluemap.common.serverinterface.Player;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.serverinterface.Server;
|
||||||
|
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
@ -39,15 +39,15 @@
|
|||||||
|
|
||||||
public class LivePlayersDataSupplier implements Supplier<String> {
|
public class LivePlayersDataSupplier implements Supplier<String> {
|
||||||
|
|
||||||
private final ServerInterface server;
|
private final Server server;
|
||||||
private final PluginConfig config;
|
private final PluginConfig config;
|
||||||
@Nullable private final String worldId;
|
private final ServerWorld world;
|
||||||
private final Predicate<UUID> playerFilter;
|
private final Predicate<UUID> playerFilter;
|
||||||
|
|
||||||
public LivePlayersDataSupplier(ServerInterface server, PluginConfig config, @Nullable String worldId, Predicate<UUID> playerFilter) {
|
public LivePlayersDataSupplier(Server server, PluginConfig config, ServerWorld world, Predicate<UUID> playerFilter) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.worldId = worldId;
|
this.world = world;
|
||||||
this.playerFilter = playerFilter;
|
this.playerFilter = playerFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +61,7 @@ public String get() {
|
|||||||
|
|
||||||
if (config.isLivePlayerMarkers()) {
|
if (config.isLivePlayerMarkers()) {
|
||||||
for (Player player : this.server.getOnlinePlayers()) {
|
for (Player player : this.server.getOnlinePlayers()) {
|
||||||
if (!player.isOnline()) continue;
|
boolean isCorrectWorld = player.getWorld().equals(this.world);
|
||||||
|
|
||||||
boolean isCorrectWorld = player.getWorld().equals(this.worldId);
|
|
||||||
|
|
||||||
if (config.isHideInvisible() && player.isInvisible()) continue;
|
if (config.isHideInvisible() && player.isInvisible()) continue;
|
||||||
if (config.isHideVanished() && player.isVanished()) continue;
|
if (config.isHideVanished() && player.isVanished()) continue;
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
package de.bluecolored.bluemap.common.plugin;
|
package de.bluecolored.bluemap.common.plugin;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.common.BlueMapConfigProvider;
|
import de.bluecolored.bluemap.common.BlueMapConfiguration;
|
||||||
import de.bluecolored.bluemap.common.BlueMapService;
|
import de.bluecolored.bluemap.common.BlueMapService;
|
||||||
import de.bluecolored.bluemap.common.InterruptableReentrantLock;
|
import de.bluecolored.bluemap.common.InterruptableReentrantLock;
|
||||||
import de.bluecolored.bluemap.common.MissingResourcesException;
|
import de.bluecolored.bluemap.common.MissingResourcesException;
|
||||||
@ -36,7 +36,8 @@
|
|||||||
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
|
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerEventListener;
|
import de.bluecolored.bluemap.common.serverinterface.ServerEventListener;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.serverinterface.Server;
|
||||||
|
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||||
import de.bluecolored.bluemap.common.web.*;
|
import de.bluecolored.bluemap.common.web.*;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpServer;
|
import de.bluecolored.bluemap.common.web.http.HttpServer;
|
||||||
import de.bluecolored.bluemap.core.debug.StateDumper;
|
import de.bluecolored.bluemap.core.debug.StateDumper;
|
||||||
@ -46,7 +47,9 @@
|
|||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||||
|
import de.bluecolored.bluemap.core.util.Tristate;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||||
import org.spongepowered.configurate.serialize.SerializationException;
|
import org.spongepowered.configurate.serialize.SerializationException;
|
||||||
@ -78,15 +81,12 @@ public class Plugin implements ServerEventListener {
|
|||||||
private final InterruptableReentrantLock loadingLock = new InterruptableReentrantLock();
|
private final InterruptableReentrantLock loadingLock = new InterruptableReentrantLock();
|
||||||
|
|
||||||
private final String implementationType;
|
private final String implementationType;
|
||||||
private final ServerInterface serverInterface;
|
private final Server serverInterface;
|
||||||
|
|
||||||
private BlueMapService blueMap;
|
private BlueMapService blueMap;
|
||||||
|
|
||||||
private PluginState pluginState;
|
private PluginState pluginState;
|
||||||
|
|
||||||
private Map<String, World> worlds;
|
|
||||||
private Map<String, BmMap> maps;
|
|
||||||
|
|
||||||
private RenderManager renderManager;
|
private RenderManager renderManager;
|
||||||
private HttpServer webServer;
|
private HttpServer webServer;
|
||||||
private Logger webLogger;
|
private Logger webLogger;
|
||||||
@ -101,7 +101,7 @@ public class Plugin implements ServerEventListener {
|
|||||||
|
|
||||||
private boolean loaded = false;
|
private boolean loaded = false;
|
||||||
|
|
||||||
public Plugin(String implementationType, ServerInterface serverInterface) {
|
public Plugin(String implementationType, Server serverInterface) {
|
||||||
this.implementationType = implementationType.toLowerCase();
|
this.implementationType = implementationType.toLowerCase();
|
||||||
this.serverInterface = serverInterface;
|
this.serverInterface = serverInterface;
|
||||||
|
|
||||||
@ -121,11 +121,18 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
|||||||
unload(); //ensure nothing is left running (from a failed load or something)
|
unload(); //ensure nothing is left running (from a failed load or something)
|
||||||
|
|
||||||
//load configs
|
//load configs
|
||||||
blueMap = new BlueMapService(serverInterface, new BlueMapConfigs(serverInterface), preloadedResourcePack);
|
BlueMapConfigManager configManager = BlueMapConfigManager.builder()
|
||||||
CoreConfig coreConfig = getConfigs().getCoreConfig();
|
.minecraftVersion(serverInterface.getMinecraftVersion())
|
||||||
WebserverConfig webserverConfig = getConfigs().getWebserverConfig();
|
.configRoot(serverInterface.getConfigFolder())
|
||||||
WebappConfig webappConfig = getConfigs().getWebappConfig();
|
.resourcePacksFolder(serverInterface.getConfigFolder().resolve("resourcepacks"))
|
||||||
PluginConfig pluginConfig = getConfigs().getPluginConfig();
|
.modsFolder(serverInterface.getModsFolder().orElse(null))
|
||||||
|
.useMetricsConfig(serverInterface.isMetricsEnabled() == Tristate.UNDEFINED)
|
||||||
|
.autoConfigWorlds(serverInterface.getLoadedServerWorlds())
|
||||||
|
.build();
|
||||||
|
CoreConfig coreConfig = configManager.getCoreConfig();
|
||||||
|
WebserverConfig webserverConfig = configManager.getWebserverConfig();
|
||||||
|
WebappConfig webappConfig = configManager.getWebappConfig();
|
||||||
|
PluginConfig pluginConfig = configManager.getPluginConfig();
|
||||||
|
|
||||||
//apply new file-logger config
|
//apply new file-logger config
|
||||||
if (coreConfig.getLog().getFile() != null) {
|
if (coreConfig.getLog().getFile() != null) {
|
||||||
@ -149,16 +156,19 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
|||||||
pluginState = new PluginState();
|
pluginState = new PluginState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//create bluemap-service
|
||||||
|
blueMap = new BlueMapService(configManager, preloadedResourcePack);
|
||||||
|
|
||||||
//try load resources
|
//try load resources
|
||||||
try {
|
try {
|
||||||
blueMap.getResourcePack();
|
blueMap.getOrLoadResourcePack();
|
||||||
} catch (MissingResourcesException ex) {
|
} catch (MissingResourcesException ex) {
|
||||||
Logger.global.logWarning("BlueMap is missing important resources!");
|
Logger.global.logWarning("BlueMap is missing important resources!");
|
||||||
Logger.global.logWarning("You must accept the required file download in order for BlueMap to work!");
|
Logger.global.logWarning("You must accept the required file download in order for BlueMap to work!");
|
||||||
|
|
||||||
BlueMapConfigProvider configProvider = blueMap.getConfigs();
|
BlueMapConfiguration configProvider = blueMap.getConfig();
|
||||||
if (configProvider instanceof BlueMapConfigs) {
|
if (configProvider instanceof BlueMapConfigManager) {
|
||||||
Logger.global.logWarning("Please check: " + ((BlueMapConfigs) configProvider).getConfigManager().findConfigPath(Path.of("core")).toAbsolutePath().normalize());
|
Logger.global.logWarning("Please check: " + ((BlueMapConfigManager) configProvider).getConfigManager().findConfigPath(Path.of("core")).toAbsolutePath().normalize());
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
||||||
@ -167,9 +177,8 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//load worlds and maps
|
//load maps
|
||||||
worlds = blueMap.getWorlds();
|
Map<String, BmMap> maps = blueMap.getOrLoadMaps();
|
||||||
maps = blueMap.getMaps();
|
|
||||||
|
|
||||||
//create and start webserver
|
//create and start webserver
|
||||||
if (webserverConfig.isEnabled()) {
|
if (webserverConfig.isEnabled()) {
|
||||||
@ -182,7 +191,7 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
|||||||
routingRequestHandler.register(".*", new FileRequestHandler(webroot));
|
routingRequestHandler.register(".*", new FileRequestHandler(webroot));
|
||||||
|
|
||||||
// map route
|
// map route
|
||||||
for (var mapConfigEntry : getConfigs().getMapConfigs().entrySet()) {
|
for (var mapConfigEntry : configManager.getMapConfigs().entrySet()) {
|
||||||
String id = mapConfigEntry.getKey();
|
String id = mapConfigEntry.getKey();
|
||||||
MapConfig mapConfig = mapConfigEntry.getValue();
|
MapConfig mapConfig = mapConfigEntry.getValue();
|
||||||
|
|
||||||
@ -191,7 +200,7 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
|||||||
if (map != null) {
|
if (map != null) {
|
||||||
mapRequestHandler = new MapRequestHandler(map, serverInterface, pluginConfig, Predicate.not(pluginState::isPlayerHidden));
|
mapRequestHandler = new MapRequestHandler(map, serverInterface, pluginConfig, Predicate.not(pluginState::isPlayerHidden));
|
||||||
} else {
|
} else {
|
||||||
Storage storage = blueMap.getStorage(mapConfig.getStorage());
|
Storage storage = blueMap.getOrLoadStorage(mapConfig.getStorage());
|
||||||
mapRequestHandler = new MapRequestHandler(id, storage);
|
mapRequestHandler = new MapRequestHandler(id, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,9 +456,6 @@ public void unload(boolean keepWebserver) {
|
|||||||
Logger.global.remove(DEBUG_FILE_LOG_NAME);
|
Logger.global.remove(DEBUG_FILE_LOG_NAME);
|
||||||
|
|
||||||
//clear resources
|
//clear resources
|
||||||
worlds = null;
|
|
||||||
maps = null;
|
|
||||||
|
|
||||||
pluginState = null;
|
pluginState = null;
|
||||||
|
|
||||||
//done
|
//done
|
||||||
@ -479,7 +485,7 @@ public void lightReload() throws IOException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// hold and reuse loaded resourcepack
|
// hold and reuse loaded resourcepack
|
||||||
ResourcePack preloadedResourcePack = this.blueMap.getResourcePackIfLoaded().orElse(null);
|
ResourcePack preloadedResourcePack = this.blueMap.getResourcePack();
|
||||||
|
|
||||||
unload();
|
unload();
|
||||||
load(preloadedResourcePack);
|
load(preloadedResourcePack);
|
||||||
@ -491,10 +497,12 @@ public void lightReload() throws IOException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void save() {
|
public synchronized void save() {
|
||||||
|
if (blueMap == null) return;
|
||||||
|
|
||||||
if (pluginState != null) {
|
if (pluginState != null) {
|
||||||
try {
|
try {
|
||||||
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
|
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
|
||||||
.path(blueMap.getConfigs().getCoreConfig().getData().resolve("pluginState.json"))
|
.path(blueMap.getConfig().getCoreConfig().getData().resolve("pluginState.json"))
|
||||||
.build();
|
.build();
|
||||||
loader.save(loader.createNode().set(PluginState.class, pluginState));
|
loader.save(loader.createNode().set(PluginState.class, pluginState));
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
@ -502,28 +510,32 @@ public synchronized void save() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maps != null) {
|
var maps = blueMap.getMaps();
|
||||||
for (BmMap map : maps.values()) {
|
for (BmMap map : maps.values()) {
|
||||||
map.save();
|
map.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void saveMarkerStates() {
|
public void saveMarkerStates() {
|
||||||
if (maps != null) {
|
if (blueMap == null) return;
|
||||||
|
|
||||||
|
var maps = blueMap.getMaps();
|
||||||
for (BmMap map : maps.values()) {
|
for (BmMap map : maps.values()) {
|
||||||
map.saveMarkerState();
|
map.saveMarkerState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void savePlayerStates() {
|
public void savePlayerStates() {
|
||||||
if (maps != null) {
|
if (blueMap == null) return;
|
||||||
|
|
||||||
|
var maps = blueMap.getMaps();
|
||||||
for (BmMap map : maps.values()) {
|
for (BmMap map : maps.values()) {
|
||||||
|
var serverWorld = serverInterface.getServerWorld(map.getWorld()).orElse(null);
|
||||||
|
if (serverWorld == null) continue;
|
||||||
var dataSupplier = new LivePlayersDataSupplier(
|
var dataSupplier = new LivePlayersDataSupplier(
|
||||||
serverInterface,
|
serverInterface,
|
||||||
getConfigs().getPluginConfig(),
|
getBlueMap().getConfig().getPluginConfig(),
|
||||||
map.getWorldId(),
|
serverWorld,
|
||||||
Predicate.not(pluginState::isPlayerHidden)
|
Predicate.not(pluginState::isPlayerHidden)
|
||||||
);
|
);
|
||||||
try (
|
try (
|
||||||
@ -536,7 +548,6 @@ public void savePlayerStates() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void startWatchingMap(BmMap map) {
|
public synchronized void startWatchingMap(BmMap map) {
|
||||||
stopWatchingMap(map);
|
stopWatchingMap(map);
|
||||||
@ -558,7 +569,7 @@ public synchronized void stopWatchingMap(BmMap map) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean flushWorldUpdates(World world) throws IOException {
|
public boolean flushWorldUpdates(World world) throws IOException {
|
||||||
var implWorld = serverInterface.getWorld(world.getSaveFolder()).orElse(null);
|
var implWorld = serverInterface.getServerWorld(world).orElse(null);
|
||||||
if (implWorld != null) return implWorld.persistWorldChanges();
|
if (implWorld != null) return implWorld.persistWorldChanges();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -588,8 +599,8 @@ public void run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean checkPausedByPlayerCount() {
|
public boolean checkPausedByPlayerCount() {
|
||||||
CoreConfig coreConfig = getConfigs().getCoreConfig();
|
CoreConfig coreConfig = getBlueMap().getConfig().getCoreConfig();
|
||||||
PluginConfig pluginConfig = getConfigs().getPluginConfig();
|
PluginConfig pluginConfig = getBlueMap().getConfig().getPluginConfig();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
pluginConfig.getPlayerRenderLimit() > 0 &&
|
pluginConfig.getPlayerRenderLimit() > 0 &&
|
||||||
@ -604,7 +615,12 @@ public boolean checkPausedByPlayerCount() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerInterface getServerInterface() {
|
public @Nullable World getWorld(ServerWorld serverWorld) {
|
||||||
|
String id = MCAWorld.id(serverWorld.getWorldFolder(), serverWorld.getDimension());
|
||||||
|
return getBlueMap().getWorlds().get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server getServerInterface() {
|
||||||
return serverInterface;
|
return serverInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -612,22 +628,10 @@ public BlueMapService getBlueMap() {
|
|||||||
return blueMap;
|
return blueMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlueMapConfigProvider getConfigs() {
|
|
||||||
return blueMap.getConfigs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PluginState getPluginState() {
|
public PluginState getPluginState() {
|
||||||
return pluginState;
|
return pluginState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, World> getWorlds(){
|
|
||||||
return worlds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, BmMap> getMaps(){
|
|
||||||
return maps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RenderManager getRenderManager() {
|
public RenderManager getRenderManager() {
|
||||||
return renderManager;
|
return renderManager;
|
||||||
}
|
}
|
||||||
@ -649,11 +653,14 @@ public PlayerSkinUpdater getSkinUpdater() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initFileWatcherTasks() {
|
private void initFileWatcherTasks() {
|
||||||
|
var maps = blueMap.getMaps();
|
||||||
|
if (maps != null) {
|
||||||
for (BmMap map : maps.values()) {
|
for (BmMap map : maps.values()) {
|
||||||
if (pluginState.getMapState(map).isUpdateEnabled()) {
|
if (pluginState.getMapState(map).isUpdateEnabled()) {
|
||||||
startWatchingMap(map);
|
startWatchingMap(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,14 @@
|
|||||||
package de.bluecolored.bluemap.common.plugin;
|
package de.bluecolored.bluemap.common.plugin;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask;
|
import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||||
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
@ -45,7 +47,7 @@ public class RegionFileWatchService extends Thread {
|
|||||||
private final RenderManager renderManager;
|
private final RenderManager renderManager;
|
||||||
private final WatchService watchService;
|
private final WatchService watchService;
|
||||||
|
|
||||||
private boolean verbose;
|
private final boolean verbose;
|
||||||
private volatile boolean closed;
|
private volatile boolean closed;
|
||||||
|
|
||||||
private Timer delayTimer;
|
private Timer delayTimer;
|
||||||
@ -60,7 +62,9 @@ public RegionFileWatchService(RenderManager renderManager, BmMap map, boolean ve
|
|||||||
this.closed = false;
|
this.closed = false;
|
||||||
this.scheduledUpdates = new HashMap<>();
|
this.scheduledUpdates = new HashMap<>();
|
||||||
|
|
||||||
Path folder = map.getWorld().getSaveFolder().resolve("region");
|
World world = map.getWorld();
|
||||||
|
if (!(world instanceof MCAWorld)) throw new UnsupportedOperationException("world-type is not supported");
|
||||||
|
Path folder = ((MCAWorld) world).getRegionFolder();
|
||||||
FileHelper.createDirectories(folder);
|
FileHelper.createDirectories(folder);
|
||||||
|
|
||||||
this.watchService = folder.getFileSystem().newWatchService();
|
this.watchService = folder.getFileSystem().newWatchService();
|
||||||
|
@ -30,7 +30,6 @@
|
|||||||
import de.bluecolored.bluemap.common.plugin.text.TextFormat;
|
import de.bluecolored.bluemap.common.plugin.text.TextFormat;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
|
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
|
||||||
import org.apache.commons.lang3.time.DurationFormatUtils;
|
import org.apache.commons.lang3.time.DurationFormatUtils;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
@ -106,7 +105,7 @@ public List<Text> createStatusMessage(){
|
|||||||
if (plugin.checkPausedByPlayerCount()) {
|
if (plugin.checkPausedByPlayerCount()) {
|
||||||
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
|
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
|
||||||
Text.of(TextColor.GOLD, "paused")));
|
Text.of(TextColor.GOLD, "paused")));
|
||||||
lines.add(Text.of(TextColor.GRAY, TextFormat.ITALIC, "\u00A0\u00A0\u00A0(there are " + plugin.getConfigs().getPluginConfig().getPlayerRenderLimit() + " or more players online)"));
|
lines.add(Text.of(TextColor.GRAY, TextFormat.ITALIC, "\u00A0\u00A0\u00A0(there are " + plugin.getBlueMap().getConfig().getPluginConfig().getPlayerRenderLimit() + " or more players online)"));
|
||||||
} else {
|
} else {
|
||||||
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
|
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
|
||||||
Text.of(TextColor.RED, "stopped")
|
Text.of(TextColor.RED, "stopped")
|
||||||
@ -134,20 +133,22 @@ public List<Text> createStatusMessage(){
|
|||||||
|
|
||||||
public Text worldHelperHover() {
|
public Text worldHelperHover() {
|
||||||
StringJoiner joiner = new StringJoiner("\n");
|
StringJoiner joiner = new StringJoiner("\n");
|
||||||
for (World world : plugin.getWorlds().values()) {
|
for (String worldId : plugin.getBlueMap().getWorlds().keySet()) {
|
||||||
joiner.add(world.getName());
|
joiner.add(worldId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Text.of("world").setHoverText(Text.of(TextColor.WHITE, "Available worlds: \n", TextColor.GRAY, joiner.toString()));
|
return Text.of(TextFormat.UNDERLINED, "world")
|
||||||
|
.setHoverText(Text.of(TextColor.WHITE, "Available worlds: \n", TextColor.GRAY, joiner.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text mapHelperHover() {
|
public Text mapHelperHover() {
|
||||||
StringJoiner joiner = new StringJoiner("\n");
|
StringJoiner joiner = new StringJoiner("\n");
|
||||||
for (String mapId : plugin.getMaps().keySet()) {
|
for (String mapId : plugin.getBlueMap().getMaps().keySet()) {
|
||||||
joiner.add(mapId);
|
joiner.add(mapId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Text.of("map").setHoverText(Text.of(TextColor.WHITE, "Available maps: \n", TextColor.GRAY, joiner.toString()));
|
return Text.of(TextFormat.UNDERLINED, "map")
|
||||||
|
.setHoverText(Text.of(TextColor.WHITE, "Available maps: \n", TextColor.GRAY, joiner.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Optional<RenderTask> getTaskForRef(String ref) {
|
public synchronized Optional<RenderTask> getTaskForRef(String ref) {
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.map.MapRenderState;
|
import de.bluecolored.bluemap.core.map.MapRenderState;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.world.Block;
|
import de.bluecolored.bluemap.core.world.block.Block;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -281,10 +281,10 @@ private <T> Optional<T> getOptionalArgument(CommandContext<S> context, String ar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<World> parseWorld(String worldName) {
|
private Optional<World> parseWorld(String worldId) {
|
||||||
for (World world : plugin.getWorlds().values()) {
|
for (var entry : plugin.getBlueMap().getWorlds().entrySet()) {
|
||||||
if (world.getName().equalsIgnoreCase(worldName)) {
|
if (entry.getKey().equals(worldId)) {
|
||||||
return Optional.of(world);
|
return Optional.of(entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,8 +292,8 @@ private Optional<World> parseWorld(String worldName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Optional<BmMap> parseMap(String mapId) {
|
private Optional<BmMap> parseMap(String mapId) {
|
||||||
for (BmMap map : plugin.getMaps().values()) {
|
for (BmMap map : plugin.getBlueMap().getMaps().values()) {
|
||||||
if (map.getId().equalsIgnoreCase(mapId)) {
|
if (map.getId().equals(mapId)) {
|
||||||
return Optional.of(map);
|
return Optional.of(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -416,7 +416,7 @@ public int reloadCommand(CommandContext<S> context, boolean light) {
|
|||||||
public int debugClearCacheCommand(CommandContext<S> context) {
|
public int debugClearCacheCommand(CommandContext<S> context) {
|
||||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||||
|
|
||||||
for (World world : plugin.getWorlds().values()) {
|
for (World world : plugin.getBlueMap().getWorlds().values()) {
|
||||||
world.invalidateChunkCache();
|
world.invalidateChunkCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,7 +436,7 @@ public int debugFlushCommand(CommandContext<S> context) {
|
|||||||
world = parseWorld(worldName.get()).orElse(null);
|
world = parseWorld(worldName.get()).orElse(null);
|
||||||
|
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this name: ", TextColor.WHITE, worldName.get()));
|
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this id: ", TextColor.WHITE, worldName.get()));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -482,7 +482,7 @@ public int debugBlockCommand(CommandContext<S> context) {
|
|||||||
position = new Vector3d(x.get(), y.get(), z.get());
|
position = new Vector3d(x.get(), y.get(), z.get());
|
||||||
|
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this name: ", TextColor.WHITE, worldName.get()));
|
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this id: ", TextColor.WHITE, worldName.get()));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -499,7 +499,7 @@ public int debugBlockCommand(CommandContext<S> context) {
|
|||||||
// collect and output debug info
|
// collect and output debug info
|
||||||
Vector3i blockPos = position.floor().toInt();
|
Vector3i blockPos = position.floor().toInt();
|
||||||
Block<?> block = new Block<>(world, blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
Block<?> block = new Block<>(world, blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||||
Block<?> blockBelow = new Block<>(null, 0, 0, 0).copy(block, 0, -1, 0);
|
Block<?> blockBelow = new Block<>(world, blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
|
||||||
|
|
||||||
// populate lazy-loaded values
|
// populate lazy-loaded values
|
||||||
block.getBlockState();
|
block.getBlockState();
|
||||||
@ -523,7 +523,7 @@ public int debugDumpCommand(CommandContext<S> context) {
|
|||||||
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Path file = plugin.getConfigs().getCoreConfig().getData().resolve("dump.json");
|
Path file = plugin.getBlueMap().getConfig().getCoreConfig().getData().resolve("dump.json");
|
||||||
StateDumper.global().dump(file);
|
StateDumper.global().dump(file);
|
||||||
|
|
||||||
source.sendMessage(Text.of(TextColor.GREEN, "Dump created at: " + file));
|
source.sendMessage(Text.of(TextColor.GREEN, "Dump created at: " + file));
|
||||||
@ -562,7 +562,7 @@ public int startCommand(CommandContext<S> context) {
|
|||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
plugin.getPluginState().setRenderThreadsEnabled(true);
|
plugin.getPluginState().setRenderThreadsEnabled(true);
|
||||||
|
|
||||||
plugin.getRenderManager().start(plugin.getConfigs().getCoreConfig().resolveRenderThreadCount());
|
plugin.getRenderManager().start(plugin.getBlueMap().getConfig().getCoreConfig().resolveRenderThreadCount());
|
||||||
source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads started!"));
|
source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads started!"));
|
||||||
|
|
||||||
plugin.save();
|
plugin.save();
|
||||||
@ -583,7 +583,7 @@ public int freezeCommand(CommandContext<S> context) {
|
|||||||
BmMap map = parseMap(mapString).orElse(null);
|
BmMap map = parseMap(mapString).orElse(null);
|
||||||
|
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString));
|
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this id: ", TextColor.WHITE, mapString));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,7 +624,7 @@ public int unfreezeCommand(CommandContext<S> context) {
|
|||||||
BmMap map = parseMap(mapString).orElse(null);
|
BmMap map = parseMap(mapString).orElse(null);
|
||||||
|
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString));
|
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this id: ", TextColor.WHITE, mapString));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,7 +671,8 @@ public int updateCommand(CommandContext<S> context, boolean force) {
|
|||||||
mapToRender = parseMap(worldOrMap.get()).orElse(null);
|
mapToRender = parseMap(worldOrMap.get()).orElse(null);
|
||||||
|
|
||||||
if (mapToRender == null) {
|
if (mapToRender == null) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, worldOrMap.get()));
|
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ",
|
||||||
|
helper.mapHelperHover(), " with this id: ", TextColor.WHITE, worldOrMap.get()));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -682,7 +683,8 @@ public int updateCommand(CommandContext<S> context, boolean force) {
|
|||||||
mapToRender = null;
|
mapToRender = null;
|
||||||
|
|
||||||
if (worldToRender == null) {
|
if (worldToRender == null) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to update!").setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " <world|map>")));
|
source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to update!")
|
||||||
|
.setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " <world|map>")));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -699,7 +701,8 @@ public int updateCommand(CommandContext<S> context, boolean force) {
|
|||||||
} else {
|
} else {
|
||||||
Vector3d position = source.getPosition().orElse(null);
|
Vector3d position = source.getPosition().orElse(null);
|
||||||
if (position == null) {
|
if (position == null) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "Can't detect a position from this command-source, you'll have to define x,z coordinates to update with a radius!").setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " <x> <z> " + radius)));
|
source.sendMessage(Text.of(TextColor.RED, "Can't detect a position from this command-source, you'll have to define x,z coordinates to update with a radius!")
|
||||||
|
.setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " <x> <z> " + radius)));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -714,16 +717,12 @@ public int updateCommand(CommandContext<S> context, boolean force) {
|
|||||||
try {
|
try {
|
||||||
List<BmMap> maps = new ArrayList<>();
|
List<BmMap> maps = new ArrayList<>();
|
||||||
if (worldToRender != null) {
|
if (worldToRender != null) {
|
||||||
var world = plugin.getServerInterface().getWorld(worldToRender.getSaveFolder()).orElse(null);
|
plugin.flushWorldUpdates(worldToRender);
|
||||||
if (world != null) world.persistWorldChanges();
|
for (BmMap map : plugin.getBlueMap().getMaps().values()) {
|
||||||
|
if (map.getWorld().equals(worldToRender)) maps.add(map);
|
||||||
for (BmMap map : plugin.getMaps().values()) {
|
|
||||||
if (map.getWorld().getSaveFolder().equals(worldToRender.getSaveFolder())) maps.add(map);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var world = plugin.getServerInterface().getWorld(mapToRender.getWorld().getSaveFolder()).orElse(null);
|
plugin.flushWorldUpdates(mapToRender.getWorld());
|
||||||
if (world != null) world.persistWorldChanges();
|
|
||||||
|
|
||||||
maps.add(mapToRender);
|
maps.add(mapToRender);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,7 +740,8 @@ public int updateCommand(CommandContext<S> context, boolean force) {
|
|||||||
updateTask.getRegions().forEach(region -> state.setRenderTime(region, -1));
|
updateTask.getRegions().forEach(region -> state.setRenderTime(region, -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + map.getId() + "' ", TextColor.GRAY, "(" + updateTask.getRegions().size() + " regions, ~" + updateTask.getRegions().size() * 1024L + " chunks)"));
|
source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + map.getId() + "' ",
|
||||||
|
TextColor.GRAY, "(" + updateTask.getRegions().size() + " regions, ~" + updateTask.getRegions().size() * 1024L + " chunks)"));
|
||||||
}
|
}
|
||||||
source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress."));
|
source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress."));
|
||||||
|
|
||||||
@ -790,7 +790,7 @@ public int purgeCommand(CommandContext<S> context) {
|
|||||||
BmMap map = parseMap(mapString).orElse(null);
|
BmMap map = parseMap(mapString).orElse(null);
|
||||||
|
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString));
|
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this id: ", TextColor.WHITE, mapString));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,8 +831,8 @@ public int worldsCommand(CommandContext<S> context) {
|
|||||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||||
|
|
||||||
source.sendMessage(Text.of(TextColor.BLUE, "Worlds loaded by BlueMap:"));
|
source.sendMessage(Text.of(TextColor.BLUE, "Worlds loaded by BlueMap:"));
|
||||||
for (var entry : plugin.getWorlds().entrySet()) {
|
for (var entry : plugin.getBlueMap().getWorlds().entrySet()) {
|
||||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getValue().getName()).setHoverText(Text.of(entry.getValue().getSaveFolder(), TextColor.GRAY, " (" + entry.getKey() + ")")));
|
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
@ -842,7 +842,7 @@ public int mapsCommand(CommandContext<S> context) {
|
|||||||
List<Text> lines = new ArrayList<>();
|
List<Text> lines = new ArrayList<>();
|
||||||
lines.add(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:"));
|
lines.add(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:"));
|
||||||
|
|
||||||
for (BmMap map : plugin.getMaps().values()) {
|
for (BmMap map : plugin.getBlueMap().getMaps().values()) {
|
||||||
boolean frozen = !plugin.getPluginState().getMapState(map).isUpdateEnabled();
|
boolean frozen = !plugin.getPluginState().getMapState(map).isUpdateEnabled();
|
||||||
|
|
||||||
lines.add(Text.of(TextColor.GRAY, " - ",
|
lines.add(Text.of(TextColor.GRAY, " - ",
|
||||||
@ -850,7 +850,7 @@ public int mapsCommand(CommandContext<S> context) {
|
|||||||
TextColor.GRAY, " (" + map.getName() + ")"));
|
TextColor.GRAY, " (" + map.getName() + ")"));
|
||||||
|
|
||||||
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0World: ",
|
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0World: ",
|
||||||
TextColor.DARK_GRAY, map.getWorld().getName()));
|
TextColor.DARK_GRAY, map.getWorld().getId()));
|
||||||
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Last Update: ",
|
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Last Update: ",
|
||||||
TextColor.DARK_GRAY, helper.formatTime(map.getRenderState().getLatestRenderTime())));
|
TextColor.DARK_GRAY, helper.formatTime(map.getRenderState().getLatestRenderTime())));
|
||||||
|
|
||||||
@ -868,7 +868,7 @@ public int storagesCommand(CommandContext<S> context) {
|
|||||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||||
|
|
||||||
source.sendMessage(Text.of(TextColor.BLUE, "Storages loaded by BlueMap:"));
|
source.sendMessage(Text.of(TextColor.BLUE, "Storages loaded by BlueMap:"));
|
||||||
for (var entry : plugin.getBlueMap().getConfigs().getStorageConfigs().entrySet()) {
|
for (var entry : plugin.getBlueMap().getConfig().getStorageConfigs().entrySet()) {
|
||||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getKey())
|
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getKey())
|
||||||
.setHoverText(Text.of(entry.getValue().getStorageType().name()))
|
.setHoverText(Text.of(entry.getValue().getStorageType().name()))
|
||||||
.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap storages " + entry.getKey())
|
.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap storages " + entry.getKey())
|
||||||
@ -884,9 +884,10 @@ public int storagesInfoCommand(CommandContext<S> context) {
|
|||||||
|
|
||||||
Storage storage;
|
Storage storage;
|
||||||
try {
|
try {
|
||||||
storage = plugin.getBlueMap().getStorage(storageId);
|
storage = plugin.getBlueMap().getOrLoadStorage(storageId);
|
||||||
} catch (ConfigurationException ex) {
|
} catch (ConfigurationException | InterruptedException ex) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, ex.getMessage()));
|
Logger.global.logError("Unexpected exception trying to load storage '" + storageId + "'!", ex);
|
||||||
|
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to load this storage. Please check the console for more details..."));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -894,8 +895,8 @@ public int storagesInfoCommand(CommandContext<S> context) {
|
|||||||
try {
|
try {
|
||||||
mapIds = storage.collectMapIds();
|
mapIds = storage.collectMapIds();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to access this storage. Please check the console for more details..."));
|
|
||||||
Logger.global.logError("Unexpected exception trying to load mapIds from storage '" + storageId + "'!", ex);
|
Logger.global.logError("Unexpected exception trying to load mapIds from storage '" + storageId + "'!", ex);
|
||||||
|
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to access this storage. Please check the console for more details..."));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -904,7 +905,7 @@ public int storagesInfoCommand(CommandContext<S> context) {
|
|||||||
source.sendMessage(Text.of(TextColor.GRAY, " <empty storage>"));
|
source.sendMessage(Text.of(TextColor.GRAY, " <empty storage>"));
|
||||||
} else {
|
} else {
|
||||||
for (String mapId : mapIds) {
|
for (String mapId : mapIds) {
|
||||||
BmMap map = plugin.getMaps().get(mapId);
|
BmMap map = plugin.getBlueMap().getMaps().get(mapId);
|
||||||
boolean isLoaded = map != null && map.getStorage().equals(storage);
|
boolean isLoaded = map != null && map.getStorage().equals(storage);
|
||||||
|
|
||||||
if (isLoaded) {
|
if (isLoaded) {
|
||||||
@ -925,13 +926,14 @@ public int storagesDeleteMapCommand(CommandContext<S> context) {
|
|||||||
|
|
||||||
Storage storage;
|
Storage storage;
|
||||||
try {
|
try {
|
||||||
storage = plugin.getBlueMap().getStorage(storageId);
|
storage = plugin.getBlueMap().getOrLoadStorage(storageId);
|
||||||
} catch (ConfigurationException ex) {
|
} catch (ConfigurationException | InterruptedException ex) {
|
||||||
source.sendMessage(Text.of(TextColor.RED, ex.getMessage()));
|
Logger.global.logError("Unexpected exception trying to load storage '" + storageId + "'!", ex);
|
||||||
|
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to load this storage. Please check the console for more details..."));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
BmMap map = plugin.getMaps().get(mapId);
|
BmMap map = plugin.getBlueMap().getMaps().get(mapId);
|
||||||
boolean isLoaded = map != null && map.getStorage().equals(storage);
|
boolean isLoaded = map != null && map.getStorage().equals(storage);
|
||||||
if (isLoaded) {
|
if (isLoaded) {
|
||||||
Text purgeCommand = Text.of(TextColor.WHITE, "/bluemap purge " + mapId)
|
Text purgeCommand = Text.of(TextColor.WHITE, "/bluemap purge " + mapId)
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
package de.bluecolored.bluemap.common.plugin.commands;
|
package de.bluecolored.bluemap.common.plugin.commands;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -40,7 +39,7 @@ public MapSuggestionProvider(Plugin plugin) {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getPossibleValues() {
|
public Collection<String> getPossibleValues() {
|
||||||
return new HashSet<>(plugin.getMaps().keySet());
|
return new HashSet<>(plugin.getBlueMap().getMaps().keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ public StorageSuggestionProvider(Plugin plugin) {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getPossibleValues() {
|
public Collection<String> getPossibleValues() {
|
||||||
return plugin.getBlueMap().getConfigs().getStorageConfigs().keySet();
|
return plugin.getBlueMap().getConfig().getStorageConfigs().keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
package de.bluecolored.bluemap.common.plugin.commands;
|
package de.bluecolored.bluemap.common.plugin.commands;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -41,13 +40,8 @@ public WorldOrMapSuggestionProvider(Plugin plugin) {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<String> getPossibleValues() {
|
public Collection<String> getPossibleValues() {
|
||||||
Collection<String> values = new HashSet<>();
|
Collection<String> values = new HashSet<>();
|
||||||
|
values.addAll(plugin.getBlueMap().getWorlds().keySet());
|
||||||
for (World world : plugin.getWorlds().values()) {
|
values.addAll(plugin.getBlueMap().getMaps().keySet());
|
||||||
values.add(world.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
values.addAll(plugin.getMaps().keySet());
|
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
package de.bluecolored.bluemap.common.plugin.commands;
|
package de.bluecolored.bluemap.common.plugin.commands;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -40,13 +39,7 @@ public WorldSuggestionProvider(Plugin plugin) {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getPossibleValues() {
|
public Collection<String> getPossibleValues() {
|
||||||
Collection<String> values = new HashSet<>();
|
return new HashSet<>(plugin.getBlueMap().getWorlds().keySet());
|
||||||
|
|
||||||
for (World world : plugin.getWorlds().values()) {
|
|
||||||
values.add(world.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ public CompletableFuture<Void> updateSkin(final UUID playerUuid) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, BmMap> maps = plugin.getMaps();
|
Map<String, BmMap> maps = plugin.getBlueMap().getMaps();
|
||||||
if (maps == null) {
|
if (maps == null) {
|
||||||
Logger.global.logDebug("Could not update skin, since the plugin seems not to be ready.");
|
Logger.global.logDebug("Could not update skin, since the plugin seems not to be ready.");
|
||||||
return;
|
return;
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.world.Grid;
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -27,11 +27,14 @@
|
|||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
import com.flowpowered.math.vector.Vector2l;
|
import com.flowpowered.math.vector.Vector2l;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
import de.bluecolored.bluemap.core.world.Chunk;
|
import de.bluecolored.bluemap.core.world.Chunk;
|
||||||
import de.bluecolored.bluemap.core.world.Grid;
|
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||||
import de.bluecolored.bluemap.core.world.Region;
|
import de.bluecolored.bluemap.core.world.Region;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -71,13 +74,17 @@ private synchronized void init() {
|
|||||||
Set<Vector2l> tileSet = new HashSet<>();
|
Set<Vector2l> tileSet = new HashSet<>();
|
||||||
startTime = System.currentTimeMillis();
|
startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
//Logger.global.logInfo("Starting: " + worldRegion);
|
// collect chunks
|
||||||
|
long changesSince = force ? 0 : map.getRenderState().getRenderTime(worldRegion);
|
||||||
long changesSince = 0;
|
|
||||||
if (!force) changesSince = map.getRenderState().getRenderTime(worldRegion);
|
|
||||||
|
|
||||||
Region region = map.getWorld().getRegion(worldRegion.getX(), worldRegion.getY());
|
Region region = map.getWorld().getRegion(worldRegion.getX(), worldRegion.getY());
|
||||||
Collection<Vector2i> chunks = region.listChunks(changesSince);
|
Collection<Vector2i> chunks = new ArrayList<>(1024);
|
||||||
|
try {
|
||||||
|
region.iterateAllChunks((ChunkConsumer.ListOnly) (x, z, timestamp) -> {
|
||||||
|
if (timestamp >= changesSince) chunks.add(new Vector2i(x, z));
|
||||||
|
});
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.global.logWarning("Failed to read region " + worldRegion + " from world " + map.getWorld().getName() + " (" + ex + ")");
|
||||||
|
}
|
||||||
|
|
||||||
Grid tileGrid = map.getHiresModelManager().getTileGrid();
|
Grid tileGrid = map.getHiresModelManager().getTileGrid();
|
||||||
Grid chunkGrid = map.getWorld().getChunkGrid();
|
Grid chunkGrid = map.getWorld().getChunkGrid();
|
||||||
@ -115,6 +122,10 @@ private synchronized void init() {
|
|||||||
.collect(Collectors.toCollection(ArrayDeque::new));
|
.collect(Collectors.toCollection(ArrayDeque::new));
|
||||||
|
|
||||||
if (tiles.isEmpty()) complete();
|
if (tiles.isEmpty()) complete();
|
||||||
|
else {
|
||||||
|
// preload chunks
|
||||||
|
map.getWorld().preloadRegionChunks(worldRegion.getX(), worldRegion.getY());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -132,7 +143,6 @@ public void doWork() {
|
|||||||
this.atWork++;
|
this.atWork++;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Logger.global.logInfo("Working on " + worldRegion + " - Tile " + tile);
|
|
||||||
if (tileRenderPreconditions(tile)) {
|
if (tileRenderPreconditions(tile)) {
|
||||||
map.renderTile(tile); // <- actual work
|
map.renderTile(tile); // <- actual work
|
||||||
}
|
}
|
||||||
@ -163,6 +173,7 @@ private boolean tileRenderPreconditions(Vector2i tile) {
|
|||||||
for (int z = minChunk.getY(); z <= maxChunk.getY(); z++) {
|
for (int z = minChunk.getY(); z <= maxChunk.getY(); z++) {
|
||||||
Chunk chunk = map.getWorld().getChunk(x, z);
|
Chunk chunk = map.getWorld().getChunk(x, z);
|
||||||
if (!chunk.isGenerated()) return false;
|
if (!chunk.isGenerated()) return false;
|
||||||
|
if (!chunk.hasLightData() && !map.getMapSettings().isIgnoreMissingLightData()) return false;
|
||||||
if (chunk.getInhabitedTime() >= minInhab) isInhabited = true;
|
if (chunk.getInhabitedTime() >= minInhab) isInhabited = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,8 +195,6 @@ private boolean tileRenderPreconditions(Vector2i tile) {
|
|||||||
|
|
||||||
private void complete() {
|
private void complete() {
|
||||||
map.getRenderState().setRenderTime(worldRegion, startTime);
|
map.getRenderState().setRenderTime(worldRegion, startTime);
|
||||||
|
|
||||||
//Logger.global.logInfo("Done with: " + worldRegion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -35,7 +35,7 @@ public interface Player {
|
|||||||
|
|
||||||
Text getName();
|
Text getName();
|
||||||
|
|
||||||
String getWorld();
|
ServerWorld getWorld();
|
||||||
|
|
||||||
Vector3d getPosition();
|
Vector3d getPosition();
|
||||||
|
|
||||||
@ -48,8 +48,6 @@ public interface Player {
|
|||||||
|
|
||||||
int getBlockLight();
|
int getBlockLight();
|
||||||
|
|
||||||
boolean isOnline();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return <code>true</code> if the player is sneaking.
|
* Return <code>true</code> if the player is sneaking.
|
||||||
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
|
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
|
||||||
|
@ -24,44 +24,21 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common.serverinterface;
|
package de.bluecolored.bluemap.common.serverinterface;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
|
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||||
import de.bluecolored.bluemap.core.util.Tristate;
|
import de.bluecolored.bluemap.core.util.Tristate;
|
||||||
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface ServerInterface {
|
public interface Server {
|
||||||
|
|
||||||
@DebugDump
|
@DebugDump
|
||||||
MinecraftVersion getMinecraftVersion();
|
MinecraftVersion getMinecraftVersion();
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a ServerEventListener, every method of this interface should be called on the specified events
|
|
||||||
*/
|
|
||||||
void registerListener(ServerEventListener listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all registered listeners
|
|
||||||
*/
|
|
||||||
void unregisterAllListeners();
|
|
||||||
|
|
||||||
default Optional<ServerWorld> getWorld(Path worldFolder) {
|
|
||||||
Path normalizedWorldFolder = worldFolder.toAbsolutePath().normalize();
|
|
||||||
return getLoadedWorlds().stream()
|
|
||||||
.filter(world -> world.getSaveFolder().toAbsolutePath().normalize().equals(normalizedWorldFolder))
|
|
||||||
.findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
default Optional<ServerWorld> getWorld(Object world) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@DebugDump
|
|
||||||
Collection<ServerWorld> getLoadedWorlds();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Folder containing the configurations for the plugin
|
* Returns the Folder containing the configurations for the plugin
|
||||||
*/
|
*/
|
||||||
@ -82,6 +59,40 @@ default Tristate isMetricsEnabled() {
|
|||||||
return Tristate.UNDEFINED;
|
return Tristate.UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the correct {@link ServerWorld} for a {@link World} if there is any.
|
||||||
|
*/
|
||||||
|
default Optional<ServerWorld> getServerWorld(World world) {
|
||||||
|
if (world instanceof MCAWorld) {
|
||||||
|
MCAWorld mcaWorld = (MCAWorld) world;
|
||||||
|
return getLoadedServerWorlds().stream()
|
||||||
|
.filter(serverWorld ->
|
||||||
|
serverWorld.getWorldFolder().toAbsolutePath().normalize()
|
||||||
|
.equals(mcaWorld.getWorldFolder().toAbsolutePath().normalize()) &&
|
||||||
|
serverWorld.getDimension().equals(mcaWorld.getDimension())
|
||||||
|
)
|
||||||
|
.findAny();
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the correct {@link ServerWorld} for any Object if there is any, this should return the correct ServerWorld
|
||||||
|
* for any implementation-specific object that represent or identify a world in any way.<br>
|
||||||
|
* Used for the API implementation.
|
||||||
|
*/
|
||||||
|
default Optional<ServerWorld> getServerWorld(Object world) {
|
||||||
|
if (world instanceof World)
|
||||||
|
return getServerWorld((World) world);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all loaded worlds of this server.
|
||||||
|
*/
|
||||||
|
@DebugDump
|
||||||
|
Collection<ServerWorld> getLoadedServerWorlds();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a collection of the states of players that are currently online
|
* Returns a collection of the states of players that are currently online
|
||||||
*/
|
*/
|
||||||
@ -89,9 +100,13 @@ default Tristate isMetricsEnabled() {
|
|||||||
Collection<Player> getOnlinePlayers();
|
Collection<Player> getOnlinePlayers();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the state of the player with that UUID if present<br>
|
* Registers a ServerEventListener, every method of this interface should be called on the specified events
|
||||||
* this method is only guaranteed to return a {@link Player} if the player is currently online.
|
|
||||||
*/
|
*/
|
||||||
Optional<Player> getPlayer(UUID uuid);
|
void registerListener(ServerEventListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all registered listeners
|
||||||
|
*/
|
||||||
|
void unregisterAllListeners();
|
||||||
|
|
||||||
}
|
}
|
@ -25,40 +25,23 @@
|
|||||||
package de.bluecolored.bluemap.common.serverinterface;
|
package de.bluecolored.bluemap.common.serverinterface;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface ServerWorld {
|
public interface ServerWorld {
|
||||||
|
|
||||||
@DebugDump
|
@DebugDump
|
||||||
default Optional<String> getId() {
|
Path getWorldFolder();
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@DebugDump
|
@DebugDump
|
||||||
default Optional<String> getName() {
|
Key getDimension();
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@DebugDump
|
|
||||||
Path getSaveFolder();
|
|
||||||
|
|
||||||
@DebugDump
|
|
||||||
default Dimension getDimension() {
|
|
||||||
Path saveFolder = getSaveFolder();
|
|
||||||
String lastName = saveFolder.getFileName().toString();
|
|
||||||
if (lastName.equals("DIM-1")) return Dimension.NETHER;
|
|
||||||
if (lastName.equals("DIM1")) return Dimension.END;
|
|
||||||
return Dimension.OVERWORLD;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to persist all changes that have been made in a world to disk.
|
* Attempts to persist all changes that have been made in a world to disk.
|
||||||
*
|
*
|
||||||
* @return <code>true</code> if the changes have been successfully persisted, <code>false</code> if this operation is not supported by the implementation
|
* @return <code>true</code> if the changes have been successfully persisted, <code>false</code> if this operation is not supported by the implementation
|
||||||
*
|
|
||||||
* @throws IOException if something went wrong trying to persist the changes
|
* @throws IOException if something went wrong trying to persist the changes
|
||||||
*/
|
*/
|
||||||
default boolean persistWorldChanges() throws IOException {
|
default boolean persistWorldChanges() throws IOException {
|
||||||
|
@ -24,12 +24,14 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common.web;
|
package de.bluecolored.bluemap.common.web;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpRequest;
|
import de.bluecolored.bluemap.common.web.http.HttpRequest;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpStatusCode;
|
import de.bluecolored.bluemap.common.web.http.HttpStatusCode;
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
import de.bluecolored.bluemap.core.BlueMap;
|
||||||
|
|
||||||
|
@DebugDump
|
||||||
public class BlueMapResponseModifier implements HttpRequestHandler {
|
public class BlueMapResponseModifier implements HttpRequestHandler {
|
||||||
|
|
||||||
private final HttpRequestHandler delegate;
|
private final HttpRequestHandler delegate;
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common.web;
|
package de.bluecolored.bluemap.common.web;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.common.web.http.*;
|
import de.bluecolored.bluemap.common.web.http.*;
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
|
|
||||||
@ -38,6 +39,7 @@
|
|||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@DebugDump
|
||||||
public class FileRequestHandler implements HttpRequestHandler {
|
public class FileRequestHandler implements HttpRequestHandler {
|
||||||
|
|
||||||
private final Path webRoot;
|
private final Path webRoot;
|
||||||
|
@ -24,9 +24,11 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common.web;
|
package de.bluecolored.bluemap.common.web;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.common.web.http.*;
|
import de.bluecolored.bluemap.common.web.http.*;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
|
||||||
|
@DebugDump
|
||||||
public class LoggingRequestHandler implements HttpRequestHandler {
|
public class LoggingRequestHandler implements HttpRequestHandler {
|
||||||
|
|
||||||
private final HttpRequestHandler delegate;
|
private final HttpRequestHandler delegate;
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
import de.bluecolored.bluemap.common.config.PluginConfig;
|
import de.bluecolored.bluemap.common.config.PluginConfig;
|
||||||
import de.bluecolored.bluemap.common.live.LiveMarkersDataSupplier;
|
import de.bluecolored.bluemap.common.live.LiveMarkersDataSupplier;
|
||||||
import de.bluecolored.bluemap.common.live.LivePlayersDataSupplier;
|
import de.bluecolored.bluemap.common.live.LivePlayersDataSupplier;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.serverinterface.Server;
|
||||||
|
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -38,9 +39,9 @@
|
|||||||
|
|
||||||
public class MapRequestHandler extends RoutingRequestHandler {
|
public class MapRequestHandler extends RoutingRequestHandler {
|
||||||
|
|
||||||
public MapRequestHandler(BmMap map, ServerInterface serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
public MapRequestHandler(BmMap map, Server serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
||||||
this(map.getId(), map.getStorage(),
|
this(map.getId(), map.getStorage(),
|
||||||
new LivePlayersDataSupplier(serverInterface, pluginConfig, map.getWorldId(), playerFilter),
|
createPlayersDataSupplier(map, serverInterface, pluginConfig, playerFilter),
|
||||||
new LiveMarkersDataSupplier(map.getMarkerSets()));
|
new LiveMarkersDataSupplier(map.getMarkerSets()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,4 +68,10 @@ public MapRequestHandler(String mapId, Storage mapStorage,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @Nullable LivePlayersDataSupplier createPlayersDataSupplier(BmMap map, Server serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
||||||
|
ServerWorld world = serverInterface.getServerWorld(map.getWorld()).orElse(null);
|
||||||
|
if (world == null) return null;
|
||||||
|
return new LivePlayersDataSupplier(serverInterface, pluginConfig, world, playerFilter);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
import de.bluecolored.bluemap.api.ContentTypeRegistry;
|
import de.bluecolored.bluemap.api.ContentTypeRegistry;
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.common.web.http.*;
|
import de.bluecolored.bluemap.common.web.http.*;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
@ -41,6 +42,7 @@
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@DebugDump
|
||||||
public class MapStorageRequestHandler implements HttpRequestHandler {
|
public class MapStorageRequestHandler implements HttpRequestHandler {
|
||||||
|
|
||||||
private static final Pattern TILE_PATTERN = Pattern.compile("tiles/([\\d/]+)/x(-?[\\d/]+)z(-?[\\d/]+).*");
|
private static final Pattern TILE_PATTERN = Pattern.compile("tiles/([\\d/]+)/x(-?[\\d/]+)z(-?[\\d/]+).*");
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common.web;
|
package de.bluecolored.bluemap.common.web;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpRequest;
|
import de.bluecolored.bluemap.common.web.http.HttpRequest;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
||||||
@ -34,6 +35,7 @@
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@DebugDump
|
||||||
public class RoutingRequestHandler implements HttpRequestHandler {
|
public class RoutingRequestHandler implements HttpRequestHandler {
|
||||||
|
|
||||||
public LinkedList<Route> routes;
|
public LinkedList<Route> routes;
|
||||||
@ -77,6 +79,7 @@ public HttpResponse handle(HttpRequest request) {
|
|||||||
return new HttpResponse(HttpStatusCode.BAD_REQUEST);
|
return new HttpResponse(HttpStatusCode.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DebugDump
|
||||||
private static class Route {
|
private static class Route {
|
||||||
|
|
||||||
private final Pattern routePattern;
|
private final Pattern routePattern;
|
||||||
|
@ -24,8 +24,11 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common.web.http;
|
package de.bluecolored.bluemap.common.web.http;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@DebugDump
|
||||||
public class HttpServer extends Server {
|
public class HttpServer extends Server {
|
||||||
|
|
||||||
private final HttpRequestHandler requestHandler;
|
private final HttpRequestHandler requestHandler;
|
||||||
|
@ -3,16 +3,20 @@
|
|||||||
## Map-Config ##
|
## Map-Config ##
|
||||||
## ##
|
## ##
|
||||||
|
|
||||||
# The name of this map
|
|
||||||
# This defines the display name of this map, you can change this at any time.
|
|
||||||
# Default is the id of this map
|
|
||||||
name: "${name}"
|
|
||||||
|
|
||||||
# The path to the save-folder of the world to render.
|
# The path to the save-folder of the world to render.
|
||||||
# (If this is not defined (commented out or removed), the map will be only registered to the web-server and the web-app
|
# (If this is not defined (commented out or removed), the map will be only registered to the web-server and the web-app
|
||||||
# but not rendered or loaded by BlueMap. This can be used to display a map that has been rendered somewhere else.)
|
# but not rendered or loaded by BlueMap. This can be used to display a map that has been rendered somewhere else.)
|
||||||
world: "${world}"
|
world: "${world}"
|
||||||
|
|
||||||
|
# The dimension of the world. Can be "minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"
|
||||||
|
# or any dimension-key introduced by a mod or datapack.
|
||||||
|
dimension: "${dimension}"
|
||||||
|
|
||||||
|
# The display-name of this map -> how this map will be named on the webapp.
|
||||||
|
# You can change this at any time.
|
||||||
|
# Default is the id of this map
|
||||||
|
name: "${name}"
|
||||||
|
|
||||||
# A lower value makes the map sorted first (in lists and menus), a higher value makes it sorted later.
|
# A lower value makes the map sorted first (in lists and menus), a higher value makes it sorted later.
|
||||||
# The value needs to be an integer but it can be negative.
|
# The value needs to be an integer but it can be negative.
|
||||||
# You can change this at any time.
|
# You can change this at any time.
|
||||||
@ -40,14 +44,6 @@ void-color: "${void-color}"
|
|||||||
# Default is 0
|
# Default is 0
|
||||||
ambient-light: ${ambient-light}
|
ambient-light: ${ambient-light}
|
||||||
|
|
||||||
# Defines the skylight level that the sky of the world is emitting.
|
|
||||||
# This should always be equivalent to the maximum in-game sky-light for that world!
|
|
||||||
# If this is a normal overworld dimension, set this to 15 (max).
|
|
||||||
# If this is a normal nether or end dimension, set this to 0 (min).
|
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is 15
|
|
||||||
world-sky-light: ${world-sky-light}
|
|
||||||
|
|
||||||
# BlueMap tries to omit all blocks that are below this Y-level and are not visible from above-ground.
|
# BlueMap tries to omit all blocks that are below this Y-level and are not visible from above-ground.
|
||||||
# More specific: Block-Faces that have a sunlight/skylight value of 0 are removed.
|
# More specific: Block-Faces that have a sunlight/skylight value of 0 are removed.
|
||||||
# This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible.
|
# This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible.
|
||||||
|
@ -27,7 +27,12 @@ fun String.runCommand(): String = ProcessBuilder(split("\\s(?=(?:[^'\"`]*(['\"`]
|
|||||||
}
|
}
|
||||||
|
|
||||||
val gitHash = "git rev-parse --verify HEAD".runCommand()
|
val gitHash = "git rev-parse --verify HEAD".runCommand()
|
||||||
val clean = "git status --porcelain".runCommand().isEmpty()
|
var clean = false;
|
||||||
|
try {
|
||||||
|
clean = "git status --porcelain".runCommand().isEmpty();
|
||||||
|
} catch (ex: TimeoutException) {
|
||||||
|
println("Failed to run 'git status --porcelain', assuming dirty version.")
|
||||||
|
}
|
||||||
val lastTag = if ("git tag".runCommand().isEmpty()) "" else "git describe --tags --abbrev=0".runCommand()
|
val lastTag = if ("git tag".runCommand().isEmpty()) "" else "git describe --tags --abbrev=0".runCommand()
|
||||||
val lastVersion = if (lastTag.isEmpty()) "dev" else lastTag.substring(1) // remove the leading 'v'
|
val lastVersion = if (lastTag.isEmpty()) "dev" else lastTag.substring(1) // remove the leading 'v'
|
||||||
val commits = "git rev-list --count $lastTag..HEAD".runCommand()
|
val commits = "git rev-list --count $lastTag..HEAD".runCommand()
|
||||||
@ -61,16 +66,22 @@ dependencies {
|
|||||||
api ("commons-io:commons-io:2.5")
|
api ("commons-io:commons-io:2.5")
|
||||||
api ("org.spongepowered:configurate-hocon:4.1.2")
|
api ("org.spongepowered:configurate-hocon:4.1.2")
|
||||||
api ("org.spongepowered:configurate-gson:4.1.2")
|
api ("org.spongepowered:configurate-gson:4.1.2")
|
||||||
api ("com.github.Querz:NBT:4.0")
|
api ("com.github.BlueMap-Minecraft:BlueNBT:v1.3.0")
|
||||||
api ("org.apache.commons:commons-dbcp2:2.9.0")
|
api ("org.apache.commons:commons-dbcp2:2.9.0")
|
||||||
api ("io.airlift:aircompressor:0.24")
|
api ("io.airlift:aircompressor:0.24")
|
||||||
|
api ("org.lz4:lz4-java:1.8.0")
|
||||||
|
|
||||||
api ("de.bluecolored.bluemap.api:BlueMapAPI")
|
api ("de.bluecolored.bluemap.api:BlueMapAPI")
|
||||||
|
|
||||||
compileOnly ("org.jetbrains:annotations:23.0.0")
|
compileOnly ("org.jetbrains:annotations:23.0.0")
|
||||||
|
compileOnly ("org.projectlombok:lombok:1.18.28")
|
||||||
|
|
||||||
|
annotationProcessor ("org.projectlombok:lombok:1.18.28")
|
||||||
|
|
||||||
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
|
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
|
||||||
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
||||||
|
testCompileOnly ("org.projectlombok:lombok:1.18.28")
|
||||||
|
testAnnotationProcessor ("org.projectlombok:lombok:1.18.28")
|
||||||
}
|
}
|
||||||
|
|
||||||
spotless {
|
spotless {
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
package de.bluecolored.bluemap.core.map;
|
package de.bluecolored.bluemap.core.map;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
|
import com.google.gson.FieldNamingPolicy;
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.api.gson.MarkerGson;
|
import de.bluecolored.bluemap.api.gson.MarkerGson;
|
||||||
@ -35,7 +37,7 @@
|
|||||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.world.Grid;
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -55,9 +57,13 @@ public class BmMap {
|
|||||||
public static final String META_FILE_MARKERS = "live/markers.json";
|
public static final String META_FILE_MARKERS = "live/markers.json";
|
||||||
public static final String META_FILE_PLAYERS = "live/players.json";
|
public static final String META_FILE_PLAYERS = "live/players.json";
|
||||||
|
|
||||||
|
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||||
|
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
|
||||||
|
.create();
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String worldId;
|
|
||||||
private final World world;
|
private final World world;
|
||||||
private final Storage storage;
|
private final Storage storage;
|
||||||
private final MapSettings mapSettings;
|
private final MapSettings mapSettings;
|
||||||
@ -76,10 +82,9 @@ public class BmMap {
|
|||||||
private long renderTimeSumNanos;
|
private long renderTimeSumNanos;
|
||||||
private long tilesRendered;
|
private long tilesRendered;
|
||||||
|
|
||||||
public BmMap(String id, String name, String worldId, World world, Storage storage, ResourcePack resourcePack, MapSettings settings) throws IOException {
|
public BmMap(String id, String name, World world, Storage storage, ResourcePack resourcePack, MapSettings settings) throws IOException {
|
||||||
this.id = Objects.requireNonNull(id);
|
this.id = Objects.requireNonNull(id);
|
||||||
this.name = Objects.requireNonNull(name);
|
this.name = Objects.requireNonNull(name);
|
||||||
this.worldId = Objects.requireNonNull(worldId);
|
|
||||||
this.world = Objects.requireNonNull(world);
|
this.world = Objects.requireNonNull(world);
|
||||||
this.storage = Objects.requireNonNull(storage);
|
this.storage = Objects.requireNonNull(storage);
|
||||||
this.resourcePack = Objects.requireNonNull(resourcePack);
|
this.resourcePack = Objects.requireNonNull(resourcePack);
|
||||||
@ -197,10 +202,7 @@ private void saveMapSettings() {
|
|||||||
OutputStream out = storage.writeMeta(id, META_FILE_SETTINGS);
|
OutputStream out = storage.writeMeta(id, META_FILE_SETTINGS);
|
||||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
||||||
) {
|
) {
|
||||||
ResourcesGson.addAdapter(new GsonBuilder())
|
GSON.toJson(this, writer);
|
||||||
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
|
|
||||||
.create()
|
|
||||||
.toJson(this, writer);
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.global.logError("Failed to save settings for map '" + getId() + "'!", ex);
|
Logger.global.logError("Failed to save settings for map '" + getId() + "'!", ex);
|
||||||
}
|
}
|
||||||
@ -235,10 +237,6 @@ public String getName() {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getWorldId() {
|
|
||||||
return worldId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public World getWorld() {
|
public World getWorld() {
|
||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,13 @@
|
|||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface MapSettings extends RenderSettings {
|
public interface MapSettings extends RenderSettings {
|
||||||
|
|
||||||
int getSorting();
|
int getSorting();
|
||||||
|
|
||||||
Optional<Vector2i> getStartPos();
|
@Nullable Vector2i getStartPos();
|
||||||
|
|
||||||
String getSkyColor();
|
String getSkyColor();
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class MapSettingsSerializer implements JsonSerializer<BmMap> {
|
public class MapSettingsSerializer implements JsonSerializer<BmMap> {
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ public JsonElement serialize(BmMap map, Type typeOfSrc, JsonSerializationContext
|
|||||||
root.add("lowres", lowres);
|
root.add("lowres", lowres);
|
||||||
|
|
||||||
// startPos
|
// startPos
|
||||||
Vector2i startPos = map.getMapSettings().getStartPos()
|
Vector2i startPos = Optional.ofNullable(map.getMapSettings().getStartPos())
|
||||||
.orElse(map.getWorld().getSpawnPoint().toVector2(true));
|
.orElse(map.getWorld().getSpawnPoint().toVector2(true));
|
||||||
root.add("startPos", context.serialize(startPos));
|
root.add("startPos", context.serialize(startPos));
|
||||||
|
|
||||||
|
@ -24,6 +24,9 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.map;
|
package de.bluecolored.bluemap.core.map;
|
||||||
|
|
||||||
|
import com.google.gson.FieldNamingPolicy;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonIOException;
|
import com.google.gson.JsonIOException;
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
@ -43,6 +46,10 @@
|
|||||||
@DebugDump
|
@DebugDump
|
||||||
public class TextureGallery {
|
public class TextureGallery {
|
||||||
|
|
||||||
|
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||||
|
.create();
|
||||||
|
|
||||||
private final Map<ResourcePath<Texture>, TextureMapping> textureMappings;
|
private final Map<ResourcePath<Texture>, TextureMapping> textureMappings;
|
||||||
private int nextId;
|
private int nextId;
|
||||||
|
|
||||||
@ -93,7 +100,7 @@ public void writeTexturesFile(OutputStream out) throws IOException {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try (Writer writer = new OutputStreamWriter(out)) {
|
try (Writer writer = new OutputStreamWriter(out)) {
|
||||||
ResourcesGson.INSTANCE.toJson(textures, Texture[].class, writer);
|
GSON.toJson(textures, Texture[].class, writer);
|
||||||
} catch (JsonIOException ex) {
|
} catch (JsonIOException ex) {
|
||||||
throw new IOException(ex);
|
throw new IOException(ex);
|
||||||
}
|
}
|
||||||
@ -102,7 +109,7 @@ public void writeTexturesFile(OutputStream out) throws IOException {
|
|||||||
public static TextureGallery readTexturesFile(InputStream in) throws IOException {
|
public static TextureGallery readTexturesFile(InputStream in) throws IOException {
|
||||||
TextureGallery gallery = new TextureGallery();
|
TextureGallery gallery = new TextureGallery();
|
||||||
try (Reader reader = new InputStreamReader(in)) {
|
try (Reader reader = new InputStreamReader(in)) {
|
||||||
Texture[] textures = ResourcesGson.INSTANCE.fromJson(reader, Texture[].class);
|
Texture[] textures = GSON.fromJson(reader, Texture[].class);
|
||||||
if (textures == null) throw new IOException("Texture data is empty!");
|
if (textures == null) throw new IOException("Texture data is empty!");
|
||||||
gallery.nextId = textures.length;
|
gallery.nextId = textures.length;
|
||||||
for (int ordinal = 0; ordinal < textures.length; ordinal++) {
|
for (int ordinal = 0; ordinal < textures.length; ordinal++) {
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.world.Grid;
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -30,7 +30,8 @@
|
|||||||
import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory;
|
import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
import de.bluecolored.bluemap.core.world.Chunk;
|
||||||
|
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
public class HiresModelRenderer {
|
public class HiresModelRenderer {
|
||||||
@ -68,13 +69,14 @@ public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileM
|
|||||||
for (z = min.getZ(); z <= max.getZ(); z++){
|
for (z = min.getZ(); z <= max.getZ(); z++){
|
||||||
|
|
||||||
maxHeight = 0;
|
maxHeight = 0;
|
||||||
topBlockLight = 0f;
|
topBlockLight = 0;
|
||||||
|
|
||||||
columnColor.set(0, 0, 0, 0, true);
|
columnColor.set(0, 0, 0, 0, true);
|
||||||
|
|
||||||
if (renderSettings.isInsideRenderBoundaries(x, z)) {
|
if (renderSettings.isInsideRenderBoundaries(x, z)) {
|
||||||
minY = Math.max(min.getY(), world.getMinY(x, z));
|
Chunk chunk = world.getChunkAtBlock(x, z);
|
||||||
maxY = Math.min(max.getY(), world.getMaxY(x, z));
|
minY = Math.max(min.getY(), chunk.getMinY(x, z));
|
||||||
|
maxY = Math.min(max.getY(), chunk.getMaxY(x, z));
|
||||||
|
|
||||||
for (y = minY; y <= maxY; y++) {
|
for (y = minY; y <= maxY; y++) {
|
||||||
block.set(x, y, z);
|
block.set(x, y, z);
|
||||||
|
@ -65,11 +65,6 @@ default Vector3i getMaxPos() {
|
|||||||
*/
|
*/
|
||||||
float getAmbientLight();
|
float getAmbientLight();
|
||||||
|
|
||||||
/**
|
|
||||||
* The sky-light level of this world (0-15)
|
|
||||||
*/
|
|
||||||
int getWorldSkyLight();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The same as the maximum height, but blocks that are above this value are treated as AIR.<br>
|
* The same as the maximum height, but blocks that are above this value are treated as AIR.<br>
|
||||||
* This leads to the top-faces being rendered instead of them being culled.
|
* This leads to the top-faces being rendered instead of them being culled.
|
||||||
@ -78,6 +73,10 @@ default boolean isRenderEdges() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean isIgnoreMissingLightData() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
default boolean isInsideRenderBoundaries(int x, int z) {
|
default boolean isInsideRenderBoundaries(int x, int z) {
|
||||||
Vector3i min = getMinPos();
|
Vector3i min = getMinPos();
|
||||||
Vector3i max = getMaxPos();
|
Vector3i max = getMaxPos();
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
|
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant;
|
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant;
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||||
import de.bluecolored.bluemap.core.world.BlockState;
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -42,9 +42,9 @@
|
|||||||
import de.bluecolored.bluemap.core.util.math.MatrixM3f;
|
import de.bluecolored.bluemap.core.util.math.MatrixM3f;
|
||||||
import de.bluecolored.bluemap.core.util.math.VectorM2f;
|
import de.bluecolored.bluemap.core.util.math.VectorM2f;
|
||||||
import de.bluecolored.bluemap.core.util.math.VectorM3f;
|
import de.bluecolored.bluemap.core.util.math.VectorM3f;
|
||||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||||
import de.bluecolored.bluemap.core.world.BlockState;
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
import de.bluecolored.bluemap.core.world.ExtendedBlock;
|
import de.bluecolored.bluemap.core.world.block.ExtendedBlock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A model builder for all liquid blocks
|
* A model builder for all liquid blocks
|
||||||
@ -71,7 +71,6 @@ public class LiquidModelBuilder {
|
|||||||
private BlockModel modelResource;
|
private BlockModel modelResource;
|
||||||
private BlockModelView blockModel;
|
private BlockModelView blockModel;
|
||||||
private Color blockColor;
|
private Color blockColor;
|
||||||
private boolean isCave;
|
|
||||||
|
|
||||||
public LiquidModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
public LiquidModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
||||||
this.resourcePack = resourcePack;
|
this.resourcePack = resourcePack;
|
||||||
@ -100,17 +99,19 @@ public void build(BlockNeighborhood<?> block, BlockState blockState, Variant var
|
|||||||
this.blockModel = blockModel;
|
this.blockModel = blockModel;
|
||||||
this.blockColor = color;
|
this.blockColor = color;
|
||||||
|
|
||||||
this.isCave =
|
|
||||||
this.block.getY() < renderSettings.getRemoveCavesBelowY() &&
|
|
||||||
this.block.getY() < block.getChunk().getOceanFloorY(block.getX(), block.getZ()) + renderSettings.getCaveDetectionOceanFloor();
|
|
||||||
|
|
||||||
build();
|
build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Color tintcolor = new Color();
|
private final Color tintcolor = new Color();
|
||||||
private void build() {
|
private void build() {
|
||||||
|
int blockLight = block.getBlockLightLevel();
|
||||||
|
int sunLight = block.getSunLightLevel();
|
||||||
|
|
||||||
// filter out blocks that are in a "cave" that should not be rendered
|
// filter out blocks that are in a "cave" that should not be rendered
|
||||||
if (this.isCave && (renderSettings.isCaveDetectionUsesBlockLight() ? block.getBlockLightLevel() : block.getSunLightLevel()) == 0f) return;
|
if (
|
||||||
|
this.block.isRemoveIfCave() &&
|
||||||
|
(renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0
|
||||||
|
) return;
|
||||||
|
|
||||||
int level = blockState.getLiquidLevel();
|
int level = blockState.getLiquidLevel();
|
||||||
if (level < 8 && !(level == 0 && isSameLiquid(block.getNeighborBlock(0, 1, 0)))){
|
if (level < 8 && !(level == 0 && isSameLiquid(block.getNeighborBlock(0, 1, 0)))){
|
||||||
@ -165,7 +166,7 @@ private void build() {
|
|||||||
blockColor.multiply(tintcolor);
|
blockColor.multiply(tintcolor);
|
||||||
|
|
||||||
// apply light
|
// apply light
|
||||||
float combinedLight = Math.max(block.getSunLightLevel() / 15f, block.getBlockLightLevel() / 15f);
|
float combinedLight = Math.max(sunLight, blockLight) / 15f;
|
||||||
combinedLight = (renderSettings.getAmbientLight() + combinedLight) / (renderSettings.getAmbientLight() + 1f);
|
combinedLight = (renderSettings.getAmbientLight() + combinedLight) / (renderSettings.getAmbientLight() + 1f);
|
||||||
blockColor.r *= combinedLight;
|
blockColor.r *= combinedLight;
|
||||||
blockColor.g *= combinedLight;
|
blockColor.g *= combinedLight;
|
||||||
|
@ -45,9 +45,9 @@
|
|||||||
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
|
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
|
||||||
import de.bluecolored.bluemap.core.util.math.VectorM2f;
|
import de.bluecolored.bluemap.core.util.math.VectorM2f;
|
||||||
import de.bluecolored.bluemap.core.util.math.VectorM3f;
|
import de.bluecolored.bluemap.core.util.math.VectorM3f;
|
||||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||||
import de.bluecolored.bluemap.core.world.BlockProperties;
|
import de.bluecolored.bluemap.core.world.BlockProperties;
|
||||||
import de.bluecolored.bluemap.core.world.ExtendedBlock;
|
import de.bluecolored.bluemap.core.world.block.ExtendedBlock;
|
||||||
import de.bluecolored.bluemap.core.world.LightData;
|
import de.bluecolored.bluemap.core.world.LightData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,7 +74,6 @@ public class ResourceModelBuilder {
|
|||||||
private BlockModelView blockModel;
|
private BlockModelView blockModel;
|
||||||
private Color blockColor;
|
private Color blockColor;
|
||||||
private float blockColorOpacity;
|
private float blockColorOpacity;
|
||||||
private boolean isCave;
|
|
||||||
|
|
||||||
public ResourceModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
public ResourceModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
||||||
this.resourcePack = resourcePack;
|
this.resourcePack = resourcePack;
|
||||||
@ -95,10 +94,6 @@ public void build(BlockNeighborhood<?> block, Variant variant, BlockModelView bl
|
|||||||
this.variant = variant;
|
this.variant = variant;
|
||||||
this.modelResource = variant.getModel().getResource();
|
this.modelResource = variant.getModel().getResource();
|
||||||
|
|
||||||
this.isCave =
|
|
||||||
this.block.getY() < renderSettings.getRemoveCavesBelowY() &&
|
|
||||||
this.block.getY() < block.getChunk().getOceanFloorY(block.getX(), block.getZ()) + renderSettings.getCaveDetectionOceanFloor();
|
|
||||||
|
|
||||||
this.tintColor.set(0, 0, 0, -1, true);
|
this.tintColor.set(0, 0, 0, -1, true);
|
||||||
|
|
||||||
// render model
|
// render model
|
||||||
@ -201,7 +196,10 @@ private void createElementFace(Element element, Direction faceDir, VectorM3f c0,
|
|||||||
int blockLight = Math.max(blockLightData.getBlockLight(), facedLightData.getBlockLight());
|
int blockLight = Math.max(blockLightData.getBlockLight(), facedLightData.getBlockLight());
|
||||||
|
|
||||||
// filter out faces that are in a "cave" that should not be rendered
|
// filter out faces that are in a "cave" that should not be rendered
|
||||||
if (isCave && (renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0f) return;
|
if (
|
||||||
|
block.isRemoveIfCave() &&
|
||||||
|
(renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0
|
||||||
|
) return;
|
||||||
|
|
||||||
// initialize the faces
|
// initialize the faces
|
||||||
blockModel.initialize();
|
blockModel.initialize();
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
import de.bluecolored.bluemap.core.world.Grid;
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
import de.bluecolored.bluemap.core.world.Grid;
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
|
|
||||||
public class LowresTileManager implements TileMetaConsumer {
|
public class LowresTileManager implements TileMetaConsumer {
|
||||||
|
|
||||||
|
@ -1,258 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mca;
|
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
|
||||||
import de.bluecolored.bluemap.core.world.Biome;
|
|
||||||
import de.bluecolored.bluemap.core.world.BlockState;
|
|
||||||
import de.bluecolored.bluemap.core.world.LightData;
|
|
||||||
import net.querz.nbt.*;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldMayBeFinal")
|
|
||||||
public class ChunkAnvil113 extends MCAChunk {
|
|
||||||
private static final long[] EMPTY_LONG_ARRAY = new long[0];
|
|
||||||
|
|
||||||
private boolean isGenerated;
|
|
||||||
private boolean hasLight;
|
|
||||||
private long inhabitedTime;
|
|
||||||
private Section[] sections;
|
|
||||||
private int[] biomes;
|
|
||||||
|
|
||||||
private long[] oceanFloorHeights = EMPTY_LONG_ARRAY;
|
|
||||||
private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag) {
|
|
||||||
super(world, chunkTag);
|
|
||||||
|
|
||||||
CompoundTag levelData = chunkTag.getCompoundTag("Level");
|
|
||||||
|
|
||||||
String status = levelData.getString("Status");
|
|
||||||
this.isGenerated = status.equals("full") ||
|
|
||||||
status.equals("fullchunk") ||
|
|
||||||
status.equals("postprocessed");
|
|
||||||
this.hasLight = isGenerated;
|
|
||||||
|
|
||||||
this.inhabitedTime = levelData.getLong("InhabitedTime");
|
|
||||||
|
|
||||||
if (!isGenerated && getWorld().isIgnoreMissingLightData()) {
|
|
||||||
isGenerated = !status.equals("empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (levelData.containsKey("Heightmaps")) {
|
|
||||||
CompoundTag heightmapsTag = levelData.getCompoundTag("Heightmaps");
|
|
||||||
this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE");
|
|
||||||
this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR");
|
|
||||||
}
|
|
||||||
|
|
||||||
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
|
|
||||||
if (levelData.containsKey("Sections")) {
|
|
||||||
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
|
|
||||||
Section section = new Section(sectionTag);
|
|
||||||
if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sections = new Section[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
|
|
||||||
if (tag instanceof ByteArrayTag) {
|
|
||||||
byte[] bs = ((ByteArrayTag) tag).getValue();
|
|
||||||
biomes = new int[bs.length];
|
|
||||||
|
|
||||||
for (int i = 0; i < bs.length; i++) {
|
|
||||||
biomes[i] = bs[i] & 0xFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (tag instanceof IntArrayTag) {
|
|
||||||
biomes = ((IntArrayTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (biomes == null || biomes.length == 0) {
|
|
||||||
biomes = new int[256];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (biomes.length < 256) {
|
|
||||||
biomes = Arrays.copyOf(biomes, 256);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isGenerated() {
|
|
||||||
return isGenerated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getInhabitedTime() {
|
|
||||||
return inhabitedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
|
|
||||||
|
|
||||||
Section section = this.sections[sectionY];
|
|
||||||
if (section == null) return BlockState.AIR;
|
|
||||||
|
|
||||||
return section.getBlockState(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
if (sectionY < 0 || sectionY >= this.sections.length)
|
|
||||||
return (y < 0) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
Section section = this.sections[sectionY];
|
|
||||||
if (section == null) return target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
return section.getLightData(x, y, z, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getBiome(int x, int y, int z) {
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
int biomeIntIndex = z * 16 + x;
|
|
||||||
|
|
||||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFormatted();
|
|
||||||
|
|
||||||
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWorldSurfaceY(int x, int z) {
|
|
||||||
if (this.worldSurfaceHeights.length < 36) return 0;
|
|
||||||
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
return (int) MCAMath.getValueFromLongStream(this.worldSurfaceHeights, z * 16 + x, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOceanFloorY(int x, int z) {
|
|
||||||
if (this.oceanFloorHeights.length < 36) return 0;
|
|
||||||
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
return (int) MCAMath.getValueFromLongStream(this.oceanFloorHeights, z * 16 + x, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Section {
|
|
||||||
private static final String AIR_ID = "minecraft:air";
|
|
||||||
|
|
||||||
private int sectionY;
|
|
||||||
private byte[] blockLight;
|
|
||||||
private byte[] skyLight;
|
|
||||||
private long[] blocks;
|
|
||||||
private BlockState[] palette;
|
|
||||||
|
|
||||||
private int bitsPerBlock;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Section(CompoundTag sectionData) {
|
|
||||||
this.sectionY = sectionData.get("Y", NumberTag.class).asInt();
|
|
||||||
this.blockLight = sectionData.getByteArray("BlockLight");
|
|
||||||
this.skyLight = sectionData.getByteArray("SkyLight");
|
|
||||||
this.blocks = sectionData.getLongArray("BlockStates");
|
|
||||||
|
|
||||||
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
|
|
||||||
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
|
|
||||||
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
|
|
||||||
|
|
||||||
//read block palette
|
|
||||||
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
|
|
||||||
if (paletteTag != null) {
|
|
||||||
this.palette = new BlockState[paletteTag.size()];
|
|
||||||
for (int i = 0; i < this.palette.length; i++) {
|
|
||||||
CompoundTag stateTag = paletteTag.get(i);
|
|
||||||
|
|
||||||
String id = stateTag.getString("Name"); //shortcut to save time and memory
|
|
||||||
if (id.equals(AIR_ID)) {
|
|
||||||
palette[i] = BlockState.AIR;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> properties = new HashMap<>();
|
|
||||||
|
|
||||||
if (stateTag.containsKey("Properties")) {
|
|
||||||
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
|
|
||||||
for (Entry<String, Tag<?>> property : propertiesTag) {
|
|
||||||
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
palette[i] = new BlockState(id, properties);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.palette = new BlockState[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSectionY() {
|
|
||||||
return sectionY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
if (palette.length == 1) return palette[0];
|
|
||||||
if (blocks.length == 0) return BlockState.AIR;
|
|
||||||
|
|
||||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
|
||||||
|
|
||||||
int blockIndex = y * 256 + z * 16 + x;
|
|
||||||
|
|
||||||
long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock);
|
|
||||||
if (value >= palette.length) {
|
|
||||||
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + " (Future occasions of this error will not be logged)");
|
|
||||||
return BlockState.MISSING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return palette[(int) value];
|
|
||||||
}
|
|
||||||
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
|
||||||
|
|
||||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
|
||||||
|
|
||||||
int blockByteIndex = y * 256 + z * 16 + x;
|
|
||||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
|
||||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
|
||||||
|
|
||||||
return target.set(
|
|
||||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
|
||||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,264 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mca;
|
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
|
||||||
import de.bluecolored.bluemap.core.world.Biome;
|
|
||||||
import de.bluecolored.bluemap.core.world.BlockState;
|
|
||||||
import de.bluecolored.bluemap.core.world.LightData;
|
|
||||||
import net.querz.nbt.*;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldMayBeFinal")
|
|
||||||
public class ChunkAnvil115 extends MCAChunk {
|
|
||||||
private static final long[] EMPTY_LONG_ARRAY = new long[0];
|
|
||||||
|
|
||||||
private boolean isGenerated;
|
|
||||||
private boolean hasLight;
|
|
||||||
private long inhabitedTime;
|
|
||||||
private Section[] sections;
|
|
||||||
private int[] biomes;
|
|
||||||
|
|
||||||
private long[] oceanFloorHeights = EMPTY_LONG_ARRAY;
|
|
||||||
private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag) {
|
|
||||||
super(world, chunkTag);
|
|
||||||
|
|
||||||
CompoundTag levelData = chunkTag.getCompoundTag("Level");
|
|
||||||
|
|
||||||
String status = levelData.getString("Status");
|
|
||||||
this.isGenerated = status.equals("full");
|
|
||||||
this.hasLight = isGenerated;
|
|
||||||
|
|
||||||
this.inhabitedTime = levelData.getLong("InhabitedTime");
|
|
||||||
|
|
||||||
if (!isGenerated && getWorld().isIgnoreMissingLightData()) {
|
|
||||||
isGenerated = !status.equals("empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (levelData.containsKey("Heightmaps")) {
|
|
||||||
CompoundTag heightmapsTag = levelData.getCompoundTag("Heightmaps");
|
|
||||||
this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE");
|
|
||||||
this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR");
|
|
||||||
}
|
|
||||||
|
|
||||||
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
|
|
||||||
if (levelData.containsKey("Sections")) {
|
|
||||||
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
|
|
||||||
Section section = new Section(sectionTag);
|
|
||||||
if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sections = new Section[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
|
|
||||||
if (tag instanceof ByteArrayTag) {
|
|
||||||
byte[] bs = ((ByteArrayTag) tag).getValue();
|
|
||||||
biomes = new int[bs.length];
|
|
||||||
|
|
||||||
for (int i = 0; i < bs.length; i++) {
|
|
||||||
biomes[i] = bs[i] & 0xFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (tag instanceof IntArrayTag) {
|
|
||||||
biomes = ((IntArrayTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (biomes == null || biomes.length == 0) {
|
|
||||||
biomes = new int[1024];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (biomes.length < 1024) {
|
|
||||||
biomes = Arrays.copyOf(biomes, 1024);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isGenerated() {
|
|
||||||
return isGenerated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getInhabitedTime() {
|
|
||||||
return inhabitedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
|
|
||||||
|
|
||||||
Section section = this.sections[sectionY];
|
|
||||||
if (section == null) return BlockState.AIR;
|
|
||||||
|
|
||||||
return section.getBlockState(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
if (sectionY < 0 || sectionY >= this.sections.length)
|
|
||||||
return (y < 0) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
Section section = this.sections[sectionY];
|
|
||||||
if (section == null) return target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
return section.getLightData(x, y, z, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getBiome(int x, int y, int z) {
|
|
||||||
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
|
|
||||||
z = (z & 0xF) / 4;
|
|
||||||
y = y / 4;
|
|
||||||
int biomeIntIndex = y * 16 + z * 4 + x;
|
|
||||||
|
|
||||||
if (biomeIntIndex < 0) return Biome.DEFAULT.getFormatted();
|
|
||||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFormatted();
|
|
||||||
|
|
||||||
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxY(int x, int z) {
|
|
||||||
return sections.length * 16 + 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWorldSurfaceY(int x, int z) {
|
|
||||||
if (this.worldSurfaceHeights.length < 36) return 0;
|
|
||||||
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
return (int) MCAMath.getValueFromLongStream(this.worldSurfaceHeights, z * 16 + x, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOceanFloorY(int x, int z) {
|
|
||||||
if (this.oceanFloorHeights.length < 36) return 0;
|
|
||||||
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
return (int) MCAMath.getValueFromLongStream(this.oceanFloorHeights, z * 16 + x, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Section {
|
|
||||||
private static final String AIR_ID = "minecraft:air";
|
|
||||||
|
|
||||||
private int sectionY;
|
|
||||||
private byte[] blockLight;
|
|
||||||
private byte[] skyLight;
|
|
||||||
private long[] blocks;
|
|
||||||
private BlockState[] palette;
|
|
||||||
|
|
||||||
private int bitsPerBlock;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Section(CompoundTag sectionData) {
|
|
||||||
this.sectionY = sectionData.get("Y", NumberTag.class).asInt();
|
|
||||||
this.blockLight = sectionData.getByteArray("BlockLight");
|
|
||||||
this.skyLight = sectionData.getByteArray("SkyLight");
|
|
||||||
this.blocks = sectionData.getLongArray("BlockStates");
|
|
||||||
|
|
||||||
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
|
|
||||||
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
|
|
||||||
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
|
|
||||||
|
|
||||||
//read block palette
|
|
||||||
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
|
|
||||||
if (paletteTag != null) {
|
|
||||||
this.palette = new BlockState[paletteTag.size()];
|
|
||||||
for (int i = 0; i < this.palette.length; i++) {
|
|
||||||
CompoundTag stateTag = paletteTag.get(i);
|
|
||||||
|
|
||||||
String id = stateTag.getString("Name"); //shortcut to save time and memory
|
|
||||||
if (id.equals(AIR_ID)) {
|
|
||||||
palette[i] = BlockState.AIR;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> properties = new HashMap<>();
|
|
||||||
|
|
||||||
if (stateTag.containsKey("Properties")) {
|
|
||||||
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
|
|
||||||
for (Entry<String, Tag<?>> property : propertiesTag) {
|
|
||||||
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
palette[i] = new BlockState(id, properties);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.palette = new BlockState[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSectionY() {
|
|
||||||
return sectionY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
if (palette.length == 1) return palette[0];
|
|
||||||
if (blocks.length == 0) return BlockState.AIR;
|
|
||||||
|
|
||||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
|
||||||
|
|
||||||
int blockIndex = y * 256 + z * 16 + x;
|
|
||||||
|
|
||||||
long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock);
|
|
||||||
if (value >= palette.length) {
|
|
||||||
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + " (Future occasions of this error will not be logged)");
|
|
||||||
return BlockState.MISSING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return palette[(int) value];
|
|
||||||
}
|
|
||||||
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
|
||||||
|
|
||||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
|
||||||
|
|
||||||
int blockByteIndex = y * 256 + z * 16 + x;
|
|
||||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
|
||||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
|
||||||
|
|
||||||
return target.set(
|
|
||||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
|
||||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,293 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mca;
|
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
|
||||||
import de.bluecolored.bluemap.core.world.Biome;
|
|
||||||
import de.bluecolored.bluemap.core.world.BlockState;
|
|
||||||
import de.bluecolored.bluemap.core.world.LightData;
|
|
||||||
import net.querz.nbt.*;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldMayBeFinal")
|
|
||||||
public class ChunkAnvil116 extends MCAChunk {
|
|
||||||
private static final long[] EMPTY_LONG_ARRAY = new long[0];
|
|
||||||
|
|
||||||
private boolean isGenerated;
|
|
||||||
private boolean hasLight;
|
|
||||||
|
|
||||||
private long inhabitedTime;
|
|
||||||
|
|
||||||
private int sectionMin, sectionMax;
|
|
||||||
private Section[] sections;
|
|
||||||
|
|
||||||
private int[] biomes;
|
|
||||||
|
|
||||||
private long[] oceanFloorHeights = EMPTY_LONG_ARRAY;
|
|
||||||
private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public ChunkAnvil116(MCAWorld world, CompoundTag chunkTag) {
|
|
||||||
super(world, chunkTag);
|
|
||||||
|
|
||||||
CompoundTag levelData = chunkTag.getCompoundTag("Level");
|
|
||||||
|
|
||||||
String status = levelData.getString("Status");
|
|
||||||
this.isGenerated = status.equals("full");
|
|
||||||
this.hasLight = isGenerated;
|
|
||||||
|
|
||||||
this.inhabitedTime = levelData.getLong("InhabitedTime");
|
|
||||||
|
|
||||||
if (!isGenerated && getWorld().isIgnoreMissingLightData()) {
|
|
||||||
isGenerated = !status.equals("empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (levelData.containsKey("Heightmaps")) {
|
|
||||||
CompoundTag heightmapsTag = levelData.getCompoundTag("Heightmaps");
|
|
||||||
this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE");
|
|
||||||
this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (levelData.containsKey("Sections")) {
|
|
||||||
this.sectionMin = Integer.MAX_VALUE;
|
|
||||||
this.sectionMax = Integer.MIN_VALUE;
|
|
||||||
|
|
||||||
ListTag<CompoundTag> sectionsTag = (ListTag<CompoundTag>) levelData.getListTag("Sections");
|
|
||||||
ArrayList<Section> sectionList = new ArrayList<>(sectionsTag.size());
|
|
||||||
|
|
||||||
for (CompoundTag sectionTag : sectionsTag) {
|
|
||||||
if (sectionTag.getListTag("Palette") == null) continue; // ignore empty sections
|
|
||||||
|
|
||||||
Section section = new Section(sectionTag);
|
|
||||||
int y = section.getSectionY();
|
|
||||||
|
|
||||||
if (sectionMin > y) sectionMin = y;
|
|
||||||
if (sectionMax < y) sectionMax = y;
|
|
||||||
|
|
||||||
sectionList.add(section);
|
|
||||||
}
|
|
||||||
|
|
||||||
sections = new Section[1 + sectionMax - sectionMin];
|
|
||||||
for (Section section : sectionList) {
|
|
||||||
sections[section.sectionY - sectionMin] = section;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sections = new Section[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
|
|
||||||
if (tag instanceof ByteArrayTag) {
|
|
||||||
byte[] bs = ((ByteArrayTag) tag).getValue();
|
|
||||||
this.biomes = new int[bs.length];
|
|
||||||
|
|
||||||
for (int i = 0; i < bs.length; i++) {
|
|
||||||
biomes[i] = bs[i] & 0xFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (tag instanceof IntArrayTag) {
|
|
||||||
this.biomes = ((IntArrayTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (biomes == null) {
|
|
||||||
this.biomes = new int[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isGenerated() {
|
|
||||||
return isGenerated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getInhabitedTime() {
|
|
||||||
return inhabitedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
|
|
||||||
Section section = getSection(sectionY);
|
|
||||||
if (section == null) return BlockState.AIR;
|
|
||||||
|
|
||||||
return section.getBlockState(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
|
|
||||||
Section section = getSection(sectionY);
|
|
||||||
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
return section.getLightData(x, y, z, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getBiome(int x, int y, int z) {
|
|
||||||
if (biomes.length < 16) return Biome.DEFAULT.getFormatted();
|
|
||||||
|
|
||||||
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
|
|
||||||
z = (z & 0xF) / 4;
|
|
||||||
y = y / 4;
|
|
||||||
int biomeIntIndex = y * 16 + z * 4 + x; // TODO: fix this for 1.17+ worlds with negative y?
|
|
||||||
|
|
||||||
// shift y up/down if not in range
|
|
||||||
if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16;
|
|
||||||
if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16;
|
|
||||||
|
|
||||||
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinY(int x, int z) {
|
|
||||||
return sectionMin * 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxY(int x, int z) {
|
|
||||||
return sectionMax * 16 + 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWorldSurfaceY(int x, int z) {
|
|
||||||
if (this.worldSurfaceHeights.length < 37) return 0;
|
|
||||||
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
return (int) MCAMath.getValueFromLongArray(this.worldSurfaceHeights, z * 16 + x, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOceanFloorY(int x, int z) {
|
|
||||||
if (this.oceanFloorHeights.length < 37) return 0;
|
|
||||||
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
return (int) MCAMath.getValueFromLongArray(this.oceanFloorHeights, z * 16 + x, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Section getSection(int y) {
|
|
||||||
y -= sectionMin;
|
|
||||||
if (y < 0 || y >= this.sections.length) return null;
|
|
||||||
return this.sections[y];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Section {
|
|
||||||
private static final String AIR_ID = "minecraft:air";
|
|
||||||
|
|
||||||
private int sectionY;
|
|
||||||
private byte[] blockLight;
|
|
||||||
private byte[] skyLight;
|
|
||||||
private long[] blocks;
|
|
||||||
private BlockState[] palette;
|
|
||||||
|
|
||||||
private int bitsPerBlock;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Section(CompoundTag sectionData) {
|
|
||||||
this.sectionY = sectionData.get("Y", NumberTag.class).asInt();
|
|
||||||
this.blockLight = sectionData.getByteArray("BlockLight");
|
|
||||||
this.skyLight = sectionData.getByteArray("SkyLight");
|
|
||||||
this.blocks = sectionData.getLongArray("BlockStates");
|
|
||||||
|
|
||||||
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
|
|
||||||
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
|
|
||||||
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
|
|
||||||
|
|
||||||
//read block palette
|
|
||||||
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
|
|
||||||
if (paletteTag != null) {
|
|
||||||
this.palette = new BlockState[paletteTag.size()];
|
|
||||||
for (int i = 0; i < this.palette.length; i++) {
|
|
||||||
CompoundTag stateTag = paletteTag.get(i);
|
|
||||||
|
|
||||||
String id = stateTag.getString("Name"); //shortcut to save time and memory
|
|
||||||
if (id.equals(AIR_ID)) {
|
|
||||||
palette[i] = BlockState.AIR;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> properties = new HashMap<>();
|
|
||||||
|
|
||||||
if (stateTag.containsKey("Properties")) {
|
|
||||||
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
|
|
||||||
for (Entry<String, Tag<?>> property : propertiesTag) {
|
|
||||||
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
palette[i] = new BlockState(id, properties);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.palette = new BlockState[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSectionY() {
|
|
||||||
return sectionY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
if (palette.length == 1) return palette[0];
|
|
||||||
if (blocks.length == 0) return BlockState.AIR;
|
|
||||||
|
|
||||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
|
||||||
|
|
||||||
int blockIndex = y * 256 + z * 16 + x;
|
|
||||||
|
|
||||||
long value = MCAMath.getValueFromLongArray(blocks, blockIndex, bitsPerBlock);
|
|
||||||
if (value >= palette.length) {
|
|
||||||
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + "! (Future occasions of this error will not be logged)");
|
|
||||||
return BlockState.MISSING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return palette[(int) value];
|
|
||||||
}
|
|
||||||
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
|
||||||
|
|
||||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
|
||||||
|
|
||||||
int blockByteIndex = y * 256 + z * 16 + x;
|
|
||||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
|
||||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
|
||||||
|
|
||||||
return target.set(
|
|
||||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
|
||||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,301 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mca;
|
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
|
||||||
import de.bluecolored.bluemap.core.world.Biome;
|
|
||||||
import de.bluecolored.bluemap.core.world.BlockState;
|
|
||||||
import de.bluecolored.bluemap.core.world.LightData;
|
|
||||||
import net.querz.nbt.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldMayBeFinal")
|
|
||||||
public class ChunkAnvil118 extends MCAChunk {
|
|
||||||
private static final long[] EMPTY_LONG_ARRAY = new long[0];
|
|
||||||
private static final BlockState[] EMPTY_BLOCK_STATE_ARRAY = new BlockState[0];
|
|
||||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
|
||||||
|
|
||||||
private boolean isGenerated;
|
|
||||||
private boolean hasLight;
|
|
||||||
|
|
||||||
private long inhabitedTime;
|
|
||||||
|
|
||||||
private int sectionMin, sectionMax;
|
|
||||||
private Section[] sections;
|
|
||||||
|
|
||||||
private long[] oceanFloorHeights = EMPTY_LONG_ARRAY;
|
|
||||||
private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public ChunkAnvil118(MCAWorld world, CompoundTag chunkTag) {
|
|
||||||
super(world, chunkTag);
|
|
||||||
|
|
||||||
String status = chunkTag.getString("Status");
|
|
||||||
this.isGenerated = status.equals("full") || status.equals("minecraft:full");
|
|
||||||
this.hasLight = isGenerated;
|
|
||||||
|
|
||||||
this.inhabitedTime = chunkTag.getLong("InhabitedTime");
|
|
||||||
|
|
||||||
if (!isGenerated && getWorld().isIgnoreMissingLightData()) {
|
|
||||||
isGenerated = !status.equals("empty") && !status.equals("minecraft:empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunkTag.containsKey("Heightmaps")) {
|
|
||||||
CompoundTag heightmapsTag = chunkTag.getCompoundTag("Heightmaps");
|
|
||||||
this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE");
|
|
||||||
this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunkTag.containsKey("sections")) {
|
|
||||||
this.sectionMin = Integer.MAX_VALUE;
|
|
||||||
this.sectionMax = Integer.MIN_VALUE;
|
|
||||||
|
|
||||||
ListTag<CompoundTag> sectionsTag = (ListTag<CompoundTag>) chunkTag.getListTag("sections");
|
|
||||||
ArrayList<Section> sectionList = new ArrayList<>(sectionsTag.size());
|
|
||||||
|
|
||||||
for (CompoundTag sectionTag : sectionsTag) {
|
|
||||||
|
|
||||||
Section section = new Section(sectionTag);
|
|
||||||
int y = section.getSectionY();
|
|
||||||
|
|
||||||
if (sectionMin > y) sectionMin = y;
|
|
||||||
if (sectionMax < y) sectionMax = y;
|
|
||||||
|
|
||||||
sectionList.add(section);
|
|
||||||
}
|
|
||||||
|
|
||||||
sections = new Section[1 + sectionMax - sectionMin];
|
|
||||||
for (Section section : sectionList) {
|
|
||||||
sections[section.sectionY - sectionMin] = section;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sections = new Section[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isGenerated() {
|
|
||||||
return isGenerated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getInhabitedTime() {
|
|
||||||
return inhabitedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
|
|
||||||
Section section = getSection(sectionY);
|
|
||||||
if (section == null) return BlockState.AIR;
|
|
||||||
|
|
||||||
return section.getBlockState(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
|
|
||||||
Section section = getSection(sectionY);
|
|
||||||
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
|
|
||||||
|
|
||||||
return section.getLightData(x, y, z, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getBiome(int x, int y, int z) {
|
|
||||||
int sectionY = y >> 4;
|
|
||||||
|
|
||||||
Section section = getSection(sectionY);
|
|
||||||
if (section == null) return Biome.DEFAULT.getFormatted();
|
|
||||||
|
|
||||||
return section.getBiome(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinY(int x, int z) {
|
|
||||||
return sectionMin * 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxY(int x, int z) {
|
|
||||||
return sectionMax * 16 + 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWorldSurfaceY(int x, int z) {
|
|
||||||
if (this.worldSurfaceHeights.length < 37) return 0;
|
|
||||||
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
return (int) MCAMath.getValueFromLongArray(this.worldSurfaceHeights, z * 16 + x, 9) - 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOceanFloorY(int x, int z) {
|
|
||||||
if (this.oceanFloorHeights.length < 37) return 0;
|
|
||||||
|
|
||||||
x &= 0xF; z &= 0xF;
|
|
||||||
return (int) MCAMath.getValueFromLongArray(this.oceanFloorHeights, z * 16 + x, 9) - 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Section getSection(int y) {
|
|
||||||
y -= sectionMin;
|
|
||||||
if (y < 0 || y >= this.sections.length) return null;
|
|
||||||
return this.sections[y];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Section {
|
|
||||||
private int sectionY;
|
|
||||||
private byte[] blockLight;
|
|
||||||
private byte[] skyLight;
|
|
||||||
private long[] blocks = EMPTY_LONG_ARRAY;
|
|
||||||
private long[] biomes = EMPTY_LONG_ARRAY;
|
|
||||||
private BlockState[] blockPalette = EMPTY_BLOCK_STATE_ARRAY;
|
|
||||||
private String[] biomePalette = EMPTY_STRING_ARRAY;
|
|
||||||
|
|
||||||
private int bitsPerBlock, bitsPerBiome;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Section(CompoundTag sectionData) {
|
|
||||||
this.sectionY = sectionData.get("Y", NumberTag.class).asInt();
|
|
||||||
this.blockLight = sectionData.getByteArray("BlockLight");
|
|
||||||
this.skyLight = sectionData.getByteArray("SkyLight");
|
|
||||||
|
|
||||||
// blocks
|
|
||||||
CompoundTag blockStatesTag = sectionData.getCompoundTag("block_states");
|
|
||||||
if (blockStatesTag != null) {
|
|
||||||
// block data
|
|
||||||
this.blocks = blockStatesTag.getLongArray("data");
|
|
||||||
|
|
||||||
// block palette
|
|
||||||
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) blockStatesTag.getListTag("palette");
|
|
||||||
if (paletteTag != null) {
|
|
||||||
this.blockPalette = new BlockState[paletteTag.size()];
|
|
||||||
for (int i = 0; i < this.blockPalette.length; i++) {
|
|
||||||
blockPalette[i] = readBlockStatePaletteEntry(paletteTag.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// biomes
|
|
||||||
CompoundTag biomesTag = sectionData.getCompoundTag("biomes");
|
|
||||||
if (biomesTag != null) {
|
|
||||||
// biomes data
|
|
||||||
this.biomes = biomesTag.getLongArray("data");
|
|
||||||
|
|
||||||
// biomes palette
|
|
||||||
ListTag<StringTag> paletteTag = (ListTag<StringTag>) biomesTag.getListTag("palette");
|
|
||||||
if (paletteTag != null) {
|
|
||||||
this.biomePalette = new String[paletteTag.size()];
|
|
||||||
for (int i = 0; i < this.biomePalette.length; i++) {
|
|
||||||
biomePalette[i] = paletteTag.get(i).getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
|
|
||||||
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
|
|
||||||
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
|
|
||||||
|
|
||||||
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
|
||||||
this.bitsPerBiome = Integer.SIZE - Integer.numberOfLeadingZeros(this.biomePalette.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BlockState readBlockStatePaletteEntry(CompoundTag paletteEntry) {
|
|
||||||
String id = paletteEntry.getString("Name");
|
|
||||||
if (BlockState.AIR.getFormatted().equals(id)) return BlockState.AIR; //shortcut to save time and memory
|
|
||||||
|
|
||||||
Map<String, String> properties = new LinkedHashMap<>();
|
|
||||||
if (paletteEntry.containsKey("Properties")) {
|
|
||||||
CompoundTag propertiesTag = paletteEntry.getCompoundTag("Properties");
|
|
||||||
for (Entry<String, Tag<?>> property : propertiesTag) {
|
|
||||||
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BlockState(id, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSectionY() {
|
|
||||||
return sectionY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
if (blockPalette.length == 1) return blockPalette[0];
|
|
||||||
if (blocks.length == 0) return BlockState.AIR;
|
|
||||||
|
|
||||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
|
||||||
|
|
||||||
int blockIndex = y * 256 + z * 16 + x;
|
|
||||||
|
|
||||||
long value = MCAMath.getValueFromLongArray(blocks, blockIndex, bitsPerBlock);
|
|
||||||
if (value >= blockPalette.length) {
|
|
||||||
Logger.global.noFloodWarning("palettewarning", "Got block-palette value " + value + " but palette has size of " + blockPalette.length + "! (Future occasions of this error will not be logged)");
|
|
||||||
return BlockState.MISSING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return blockPalette[(int) value];
|
|
||||||
}
|
|
||||||
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
|
||||||
|
|
||||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
|
||||||
|
|
||||||
int blockByteIndex = y * 256 + z * 16 + x;
|
|
||||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
|
||||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
|
||||||
|
|
||||||
return target.set(
|
|
||||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
|
||||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBiome(int x, int y, int z) {
|
|
||||||
if (biomePalette.length == 0) return Biome.DEFAULT.getValue();
|
|
||||||
if (biomePalette.length == 1 || biomes.length == 0) return biomePalette[0];
|
|
||||||
|
|
||||||
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16) / 4
|
|
||||||
z = (z & 0xF) / 4;
|
|
||||||
y = (y & 0xF) / 4;
|
|
||||||
int biomeIndex = y * 16 + z * 4 + x;
|
|
||||||
|
|
||||||
long value = MCAMath.getValueFromLongArray(biomes, biomeIndex, bitsPerBiome);
|
|
||||||
if (value >= biomePalette.length) {
|
|
||||||
Logger.global.noFloodWarning("biomepalettewarning", "Got biome-palette value " + value + " but palette has size of " + biomePalette.length + "! (Future occasions of this error will not be logged)");
|
|
||||||
return Biome.DEFAULT.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
return biomePalette[(int) value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mca;
|
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.world.BlockState;
|
|
||||||
import de.bluecolored.bluemap.core.world.Chunk;
|
|
||||||
import de.bluecolored.bluemap.core.world.LightData;
|
|
||||||
import net.querz.nbt.CompoundTag;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public abstract class MCAChunk implements Chunk {
|
|
||||||
|
|
||||||
private final MCAWorld world;
|
|
||||||
private final int dataVersion;
|
|
||||||
|
|
||||||
protected MCAChunk() {
|
|
||||||
this.world = null;
|
|
||||||
this.dataVersion = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MCAChunk(MCAWorld world) {
|
|
||||||
this.world = world;
|
|
||||||
this.dataVersion = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MCAChunk(MCAWorld world, CompoundTag chunkTag) {
|
|
||||||
this.world = world;
|
|
||||||
dataVersion = chunkTag.getInt("DataVersion");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract boolean isGenerated();
|
|
||||||
|
|
||||||
public int getDataVersion() {
|
|
||||||
return dataVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract long getInhabitedTime();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract BlockState getBlockState(int x, int y, int z);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract LightData getLightData(int x, int y, int z, LightData target);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract String getBiome(int x, int y, int z);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxY(int x, int z) {
|
|
||||||
return 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinY(int x, int z) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWorldSurfaceY(int x, int z) { return 0; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOceanFloorY(int x, int z) { return 0; }
|
|
||||||
|
|
||||||
protected MCAWorld getWorld() {
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MCAChunk create(MCAWorld world, CompoundTag chunkTag) throws IOException {
|
|
||||||
int version = chunkTag.getInt("DataVersion");
|
|
||||||
|
|
||||||
if (version < 2200) return new ChunkAnvil113(world, chunkTag);
|
|
||||||
if (version < 2500) return new ChunkAnvil115(world, chunkTag);
|
|
||||||
if (version < 2844) return new ChunkAnvil116(world, chunkTag);
|
|
||||||
return new ChunkAnvil118(world, chunkTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "MCAChunk{" +
|
|
||||||
"world=" + world +
|
|
||||||
"dataVersion=" + dataVersion +
|
|
||||||
"isGenerated()=" + isGenerated() +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,289 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mca;
|
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
|
||||||
import com.flowpowered.math.vector.Vector3i;
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
|
||||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
|
||||||
import de.bluecolored.bluemap.core.mca.region.RegionType;
|
|
||||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
|
||||||
import de.bluecolored.bluemap.core.world.*;
|
|
||||||
import net.querz.nbt.CompoundTag;
|
|
||||||
import net.querz.nbt.NBTUtil;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@DebugDump
|
|
||||||
public class MCAWorld implements World {
|
|
||||||
|
|
||||||
private static final Grid CHUNK_GRID = new Grid(16);
|
|
||||||
private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID);
|
|
||||||
|
|
||||||
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
|
|
||||||
|
|
||||||
private final Path worldFolder;
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final Vector3i spawnPoint;
|
|
||||||
|
|
||||||
private final int skyLight;
|
|
||||||
private final boolean ignoreMissingLightData;
|
|
||||||
|
|
||||||
private final LoadingCache<Vector2i, Region> regionCache;
|
|
||||||
private final LoadingCache<Vector2i, Chunk> chunkCache;
|
|
||||||
|
|
||||||
public MCAWorld(Path worldFolder, int skyLight, boolean ignoreMissingLightData) throws IOException {
|
|
||||||
this.worldFolder = worldFolder.toRealPath();
|
|
||||||
this.skyLight = skyLight;
|
|
||||||
this.ignoreMissingLightData = ignoreMissingLightData;
|
|
||||||
|
|
||||||
this.regionCache = Caffeine.newBuilder()
|
|
||||||
.executor(BlueMap.THREAD_POOL)
|
|
||||||
.maximumSize(100)
|
|
||||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
|
||||||
.build(this::loadRegion);
|
|
||||||
|
|
||||||
this.chunkCache = Caffeine.newBuilder()
|
|
||||||
.executor(BlueMap.THREAD_POOL)
|
|
||||||
.maximumSize(500)
|
|
||||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
|
||||||
.build(this::loadChunk);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Path levelFile = resolveLevelFile(worldFolder);
|
|
||||||
CompoundTag level = (CompoundTag) NBTUtil.readTag(levelFile.toFile());
|
|
||||||
CompoundTag levelData = level.getCompoundTag("Data");
|
|
||||||
|
|
||||||
this.name = levelData.getString("LevelName");
|
|
||||||
|
|
||||||
this.spawnPoint = new Vector3i(
|
|
||||||
levelData.getInt("SpawnX"),
|
|
||||||
levelData.getInt("SpawnY"),
|
|
||||||
levelData.getInt("SpawnZ")
|
|
||||||
);
|
|
||||||
} catch (ClassCastException | NullPointerException ex) {
|
|
||||||
throw new IOException("Invalid level.dat format!", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Chunk getChunkAtBlock(int x, int y, int z) {
|
|
||||||
return getChunk(x >> 4, z >> 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Chunk getChunk(int x, int z) {
|
|
||||||
return getChunk(VECTOR_2_I_CACHE.get(x, z));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Chunk getChunk(Vector2i pos) {
|
|
||||||
return chunkCache.get(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Region getRegion(int x, int z) {
|
|
||||||
return getRegion(VECTOR_2_I_CACHE.get(x, z));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Region getRegion(Vector2i pos) {
|
|
||||||
return regionCache.get(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Vector2i> listRegions() {
|
|
||||||
File[] regionFiles = getRegionFolder().toFile().listFiles();
|
|
||||||
if (regionFiles == null) return Collections.emptyList();
|
|
||||||
|
|
||||||
List<Vector2i> regions = new ArrayList<>(regionFiles.length);
|
|
||||||
|
|
||||||
for (File file : regionFiles) {
|
|
||||||
if (RegionType.forFileName(file.getName()) == null) continue;
|
|
||||||
if (file.length() <= 0) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
String[] filenameParts = file.getName().split("\\.");
|
|
||||||
int rX = Integer.parseInt(filenameParts[1]);
|
|
||||||
int rZ = Integer.parseInt(filenameParts[2]);
|
|
||||||
|
|
||||||
regions.add(new Vector2i(rX, rZ));
|
|
||||||
} catch (NumberFormatException ignore) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return regions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Path getSaveFolder() {
|
|
||||||
return worldFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSkyLight() {
|
|
||||||
return skyLight;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinY(int x, int z) {
|
|
||||||
return getChunk(x >> 4, z >> 4).getMinY(x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxY(int x, int z) {
|
|
||||||
return getChunk(x >> 4, z >> 4).getMaxY(x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Grid getChunkGrid() {
|
|
||||||
return CHUNK_GRID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Grid getRegionGrid() {
|
|
||||||
return REGION_GRID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Vector3i getSpawnPoint() {
|
|
||||||
return spawnPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invalidateChunkCache() {
|
|
||||||
chunkCache.invalidateAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invalidateChunkCache(int x, int z) {
|
|
||||||
chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanUpChunkCache() {
|
|
||||||
chunkCache.cleanUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getWorldFolder() {
|
|
||||||
return worldFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path getRegionFolder() {
|
|
||||||
return worldFolder.resolve("region");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIgnoreMissingLightData() {
|
|
||||||
return ignoreMissingLightData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Region loadRegion(Vector2i regionPos) {
|
|
||||||
return loadRegion(regionPos.getX(), regionPos.getY());
|
|
||||||
}
|
|
||||||
|
|
||||||
Region loadRegion(int x, int z) {
|
|
||||||
return RegionType.loadRegion(this, getRegionFolder(), x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Chunk loadChunk(Vector2i chunkPos) {
|
|
||||||
return loadChunk(chunkPos.getX(), chunkPos.getY());
|
|
||||||
}
|
|
||||||
|
|
||||||
Chunk loadChunk(int x, int z) {
|
|
||||||
final int tries = 3;
|
|
||||||
final int tryInterval = 1000;
|
|
||||||
|
|
||||||
Exception loadException = null;
|
|
||||||
for (int i = 0; i < tries; i++) {
|
|
||||||
try {
|
|
||||||
return getRegion(x >> 5, z >> 5)
|
|
||||||
.loadChunk(x, z, ignoreMissingLightData);
|
|
||||||
} catch (IOException | RuntimeException e) {
|
|
||||||
if (loadException != null) e.addSuppressed(loadException);
|
|
||||||
loadException = e;
|
|
||||||
|
|
||||||
if (i + 1 < tries) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(tryInterval);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "):" + loadException);
|
|
||||||
return EmptyChunk.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "MCAWorld{" +
|
|
||||||
"worldFolder=" + worldFolder +
|
|
||||||
", name='" + name + '\'' +
|
|
||||||
", spawnPoint=" + spawnPoint +
|
|
||||||
", skyLight=" + skyLight +
|
|
||||||
", ignoreMissingLightData=" + ignoreMissingLightData +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path resolveLevelFile(Path worldFolder) throws IOException {
|
|
||||||
Path levelFolder = worldFolder.toRealPath();
|
|
||||||
Path levelFile = levelFolder.resolve("level.dat");
|
|
||||||
int searchDepth = 0;
|
|
||||||
|
|
||||||
while (!Files.isRegularFile(levelFile) && searchDepth < 4) {
|
|
||||||
searchDepth++;
|
|
||||||
levelFolder = levelFolder.getParent();
|
|
||||||
if (levelFolder == null) break;
|
|
||||||
|
|
||||||
levelFile = levelFolder.resolve("level.dat");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Files.isRegularFile(levelFile))
|
|
||||||
throw new FileNotFoundException("Could not find a level.dat file for this world!");
|
|
||||||
|
|
||||||
return levelFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,215 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mca.region;
|
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
|
||||||
import de.bluecolored.bluemap.core.mca.MCAChunk;
|
|
||||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
|
||||||
import de.bluecolored.bluemap.core.world.Chunk;
|
|
||||||
import de.bluecolored.bluemap.core.world.EmptyChunk;
|
|
||||||
import de.bluecolored.bluemap.core.world.Region;
|
|
||||||
import io.airlift.compress.zstd.ZstdInputStream;
|
|
||||||
import net.querz.nbt.CompoundTag;
|
|
||||||
import net.querz.nbt.Tag;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class LinearRegion implements Region {
|
|
||||||
|
|
||||||
public static final String FILE_SUFFIX = ".linear";
|
|
||||||
|
|
||||||
private static final List<Byte> SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2);
|
|
||||||
private static final long SUPERBLOCK = -4323716122432332390L;
|
|
||||||
private static final int HEADER_SIZE = 32;
|
|
||||||
private static final int FOOTER_SIZE = 8;
|
|
||||||
|
|
||||||
private final MCAWorld world;
|
|
||||||
private final Path regionFile;
|
|
||||||
private final Vector2i regionPos;
|
|
||||||
|
|
||||||
|
|
||||||
public LinearRegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
|
|
||||||
this.world = world;
|
|
||||||
this.regionFile = regionFile;
|
|
||||||
|
|
||||||
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
|
|
||||||
int rX = Integer.parseInt(filenameParts[1]);
|
|
||||||
int rZ = Integer.parseInt(filenameParts[2]);
|
|
||||||
|
|
||||||
this.regionPos = new Vector2i(rX, rZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException {
|
|
||||||
if (Files.notExists(regionFile)) return EmptyChunk.INSTANCE;
|
|
||||||
|
|
||||||
long fileLength = Files.size(regionFile);
|
|
||||||
if (fileLength == 0) return EmptyChunk.INSTANCE;
|
|
||||||
|
|
||||||
try (InputStream inputStream = Files.newInputStream(regionFile);
|
|
||||||
DataInputStream rawDataStream = new DataInputStream(inputStream)) {
|
|
||||||
|
|
||||||
long superBlock = rawDataStream.readLong();
|
|
||||||
if (superBlock != SUPERBLOCK)
|
|
||||||
throw new RuntimeException("Invalid superblock: " + superBlock + " file " + regionFile);
|
|
||||||
|
|
||||||
byte version = rawDataStream.readByte();
|
|
||||||
if (!SUPPORTED_VERSIONS.contains(version))
|
|
||||||
throw new RuntimeException("Invalid version: " + version + " file " + regionFile);
|
|
||||||
|
|
||||||
// Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused.
|
|
||||||
rawDataStream.skipBytes(11);
|
|
||||||
|
|
||||||
int dataCount = rawDataStream.readInt();
|
|
||||||
if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE)
|
|
||||||
throw new RuntimeException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
|
|
||||||
|
|
||||||
// Skip data hash (Long): Unused.
|
|
||||||
rawDataStream.skipBytes(8);
|
|
||||||
|
|
||||||
byte[] rawCompressed = new byte[dataCount];
|
|
||||||
rawDataStream.readFully(rawCompressed, 0, dataCount);
|
|
||||||
|
|
||||||
superBlock = rawDataStream.readLong();
|
|
||||||
if (superBlock != SUPERBLOCK)
|
|
||||||
throw new RuntimeException("Invalid footer superblock: " + this.regionFile);
|
|
||||||
|
|
||||||
try (DataInputStream dis = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) {
|
|
||||||
int x = chunkX - (regionPos.getX() << 5);
|
|
||||||
int z = chunkZ - (regionPos.getY() << 5);
|
|
||||||
int pos = (z << 5) + x;
|
|
||||||
int skip = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < pos; i++) {
|
|
||||||
skip += dis.readInt(); // Size of the chunk (bytes) to skip
|
|
||||||
dis.skipBytes(4); // Skip timestamps
|
|
||||||
}
|
|
||||||
|
|
||||||
int size = dis.readInt();
|
|
||||||
if (size <= 0) return EmptyChunk.INSTANCE;
|
|
||||||
|
|
||||||
dis.skipBytes(((1024 - pos - 1) << 3) + 4); // Skip current chunk 0 and unneeded other chunks zero/size
|
|
||||||
dis.skipBytes(skip); // Skip unneeded chunks data
|
|
||||||
|
|
||||||
Tag<?> tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH);
|
|
||||||
if (tag instanceof CompoundTag) {
|
|
||||||
MCAChunk chunk = MCAChunk.create(world, (CompoundTag) tag);
|
|
||||||
if (!chunk.isGenerated()) return EmptyChunk.INSTANCE;
|
|
||||||
return chunk;
|
|
||||||
} else {
|
|
||||||
throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Vector2i> listChunks(long modifiedSince) {
|
|
||||||
if (Files.notExists(regionFile)) return Collections.emptyList();
|
|
||||||
|
|
||||||
long fileLength;
|
|
||||||
try {
|
|
||||||
fileLength = Files.size(regionFile);
|
|
||||||
if (fileLength == 0) return Collections.emptyList();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.global.logWarning("Failed to read file-size for file: " + regionFile);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Vector2i> chunks = new ArrayList<>(1024); //1024 = 32 x 32 chunks per region-file
|
|
||||||
try (InputStream inputStream = Files.newInputStream(regionFile);
|
|
||||||
DataInputStream rawDataStream = new DataInputStream(inputStream)) {
|
|
||||||
|
|
||||||
long superBlock = rawDataStream.readLong();
|
|
||||||
if (superBlock != SUPERBLOCK)
|
|
||||||
throw new RuntimeException("Invalid superblock: " + superBlock + " file " + regionFile);
|
|
||||||
|
|
||||||
byte version = rawDataStream.readByte();
|
|
||||||
if (!SUPPORTED_VERSIONS.contains(version))
|
|
||||||
throw new RuntimeException("Invalid version: " + version + " file " + regionFile);
|
|
||||||
|
|
||||||
int date = (int) (modifiedSince / 1000);
|
|
||||||
|
|
||||||
// If whole region is the same - skip.
|
|
||||||
long newestTimestamp = rawDataStream.readLong();
|
|
||||||
if (newestTimestamp < date) return Collections.emptyList();
|
|
||||||
|
|
||||||
// Linear v1 files store whole region timestamp, not chunk timestamp. We need to render the whole region file.
|
|
||||||
if (version == 1) {
|
|
||||||
for(int i = 0 ; i < 1024; i++)
|
|
||||||
chunks.add(new Vector2i((regionPos.getX() << 5) + (i & 31), (regionPos.getY() << 5) + (i >> 5)));
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
// Linear v2: Chunk timestamps are here!
|
|
||||||
// Skip Compression level (Byte) + Chunk count (Short): Unused.
|
|
||||||
rawDataStream.skipBytes(3);
|
|
||||||
|
|
||||||
int dataCount = rawDataStream.readInt();
|
|
||||||
if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE)
|
|
||||||
throw new RuntimeException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
|
|
||||||
|
|
||||||
// Skip data hash (Long): Unused.
|
|
||||||
rawDataStream.skipBytes(8);
|
|
||||||
|
|
||||||
byte[] rawCompressed = new byte[dataCount];
|
|
||||||
rawDataStream.readFully(rawCompressed, 0, dataCount);
|
|
||||||
|
|
||||||
superBlock = rawDataStream.readLong();
|
|
||||||
if (superBlock != SUPERBLOCK)
|
|
||||||
throw new RuntimeException("Invalid footer SuperBlock: " + this.regionFile);
|
|
||||||
|
|
||||||
try (DataInputStream dis = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) {
|
|
||||||
for (int i = 0; i < 1024; i++) {
|
|
||||||
dis.skipBytes(4); // Skip size of the chunk
|
|
||||||
int timestamp = dis.readInt();
|
|
||||||
if (timestamp >= date) // Timestamps
|
|
||||||
chunks.add(new Vector2i((regionPos.getX() << 5) + (i & 31), (regionPos.getY() << 5) + (i >> 5)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (RuntimeException | IOException ex) {
|
|
||||||
Logger.global.logWarning("Failed to read .linear file: " + regionFile + " (" + ex + ")");
|
|
||||||
}
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Path getRegionFile() {
|
|
||||||
return regionFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRegionFileName(int regionX, int regionZ) {
|
|
||||||
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mca.region;
|
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
|
||||||
import de.bluecolored.bluemap.core.mca.MCAChunk;
|
|
||||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
|
||||||
import de.bluecolored.bluemap.core.world.Chunk;
|
|
||||||
import de.bluecolored.bluemap.core.world.EmptyChunk;
|
|
||||||
import de.bluecolored.bluemap.core.world.Region;
|
|
||||||
import net.querz.nbt.CompoundTag;
|
|
||||||
import net.querz.nbt.Tag;
|
|
||||||
import net.querz.nbt.mca.CompressionType;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class MCARegion implements Region {
|
|
||||||
|
|
||||||
public static final String FILE_SUFFIX = ".mca";
|
|
||||||
|
|
||||||
private final MCAWorld world;
|
|
||||||
private final Path regionFile;
|
|
||||||
private final Vector2i regionPos;
|
|
||||||
|
|
||||||
public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
|
|
||||||
this.world = world;
|
|
||||||
this.regionFile = regionFile;
|
|
||||||
|
|
||||||
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
|
|
||||||
int rX = Integer.parseInt(filenameParts[1]);
|
|
||||||
int rZ = Integer.parseInt(filenameParts[2]);
|
|
||||||
|
|
||||||
this.regionPos = new Vector2i(rX, rZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException {
|
|
||||||
if (Files.notExists(regionFile)) return EmptyChunk.INSTANCE;
|
|
||||||
|
|
||||||
long fileLength = Files.size(regionFile);
|
|
||||||
if (fileLength == 0) return EmptyChunk.INSTANCE;
|
|
||||||
|
|
||||||
try (RandomAccessFile raf = new RandomAccessFile(regionFile.toFile(), "r")) {
|
|
||||||
|
|
||||||
int xzChunk = Math.floorMod(chunkZ, 32) * 32 + Math.floorMod(chunkX, 32);
|
|
||||||
|
|
||||||
raf.seek(xzChunk * 4L);
|
|
||||||
int offset = raf.read() << 16;
|
|
||||||
offset |= (raf.read() & 0xFF) << 8;
|
|
||||||
offset |= raf.read() & 0xFF;
|
|
||||||
offset *= 4096;
|
|
||||||
|
|
||||||
int size = raf.readByte() * 4096;
|
|
||||||
if (size == 0) {
|
|
||||||
return EmptyChunk.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
raf.seek(offset + 4); // +4 skip chunk size
|
|
||||||
|
|
||||||
byte compressionTypeByte = raf.readByte();
|
|
||||||
CompressionType compressionType = compressionTypeByte == 3 ?
|
|
||||||
CompressionType.NONE :
|
|
||||||
CompressionType.getFromID(compressionTypeByte);
|
|
||||||
if (compressionType == null) {
|
|
||||||
throw new IOException("Invalid compression type " + compressionTypeByte);
|
|
||||||
}
|
|
||||||
|
|
||||||
DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD()))));
|
|
||||||
Tag<?> tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH);
|
|
||||||
if (tag instanceof CompoundTag) {
|
|
||||||
MCAChunk chunk = MCAChunk.create(world, (CompoundTag) tag);
|
|
||||||
if (!chunk.isGenerated()) return EmptyChunk.INSTANCE;
|
|
||||||
return chunk;
|
|
||||||
} else {
|
|
||||||
throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Vector2i> listChunks(long modifiedSince) {
|
|
||||||
if (Files.notExists(regionFile)) return Collections.emptyList();
|
|
||||||
|
|
||||||
try {
|
|
||||||
long fileLength = Files.size(regionFile);
|
|
||||||
if (fileLength == 0) return Collections.emptyList();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.global.logWarning("Failed to read file-size for file: " + regionFile);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Vector2i> chunks = new ArrayList<>(1024); //1024 = 32 x 32 chunks per region-file
|
|
||||||
|
|
||||||
try (RandomAccessFile raf = new RandomAccessFile(regionFile.toFile(), "r")) {
|
|
||||||
for (int x = 0; x < 32; x++) {
|
|
||||||
for (int z = 0; z < 32; z++) {
|
|
||||||
Vector2i chunk = new Vector2i(regionPos.getX() * 32 + x, regionPos.getY() * 32 + z);
|
|
||||||
int xzChunk = z * 32 + x;
|
|
||||||
|
|
||||||
raf.seek(xzChunk * 4 + 3);
|
|
||||||
int size = raf.readByte() * 4096;
|
|
||||||
|
|
||||||
if (size == 0) continue;
|
|
||||||
|
|
||||||
raf.seek(xzChunk * 4 + 4096);
|
|
||||||
int timestamp = raf.read() << 24;
|
|
||||||
timestamp |= (raf.read() & 0xFF) << 16;
|
|
||||||
timestamp |= (raf.read() & 0xFF) << 8;
|
|
||||||
timestamp |= raf.read() & 0xFF;
|
|
||||||
|
|
||||||
if (timestamp >= (modifiedSince / 1000)) {
|
|
||||||
chunks.add(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (RuntimeException | IOException ex) {
|
|
||||||
Logger.global.logWarning("Failed to read .mca file: " + regionFile + " (" + ex + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Path getRegionFile() {
|
|
||||||
return regionFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRegionFileName(int regionX, int regionZ) {
|
|
||||||
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -29,7 +29,7 @@
|
|||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
import de.bluecolored.bluemap.core.world.Biome;
|
import de.bluecolored.bluemap.core.world.Biome;
|
||||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@ -42,6 +42,14 @@
|
|||||||
@DebugDump
|
@DebugDump
|
||||||
public class BlockColorCalculatorFactory {
|
public class BlockColorCalculatorFactory {
|
||||||
|
|
||||||
|
private static final int
|
||||||
|
AVERAGE_MIN_X = - 2,
|
||||||
|
AVERAGE_MAX_X = 2,
|
||||||
|
AVERAGE_MIN_Y = - 1,
|
||||||
|
AVERAGE_MAX_Y = 1,
|
||||||
|
AVERAGE_MIN_Z = - 2,
|
||||||
|
AVERAGE_MAX_Z = 2;
|
||||||
|
|
||||||
private final int[] foliageMap = new int[65536];
|
private final int[] foliageMap = new int[65536];
|
||||||
private final int[] grassMap = new int[65536];
|
private final int[] grassMap = new int[65536];
|
||||||
|
|
||||||
@ -133,18 +141,12 @@ public Color getRedstoneColor(BlockNeighborhood<?> block, Color target) {
|
|||||||
public Color getWaterAverageColor(BlockNeighborhood<?> block, Color target) {
|
public Color getWaterAverageColor(BlockNeighborhood<?> block, Color target) {
|
||||||
target.set(0, 0, 0, 0, true);
|
target.set(0, 0, 0, 0, true);
|
||||||
|
|
||||||
int x, y, z,
|
int x, y, z;
|
||||||
minX = - 2,
|
|
||||||
maxX = 2,
|
|
||||||
minY = - 1,
|
|
||||||
maxY = 1,
|
|
||||||
minZ = - 2,
|
|
||||||
maxZ = 2;
|
|
||||||
|
|
||||||
Biome biome;
|
Biome biome;
|
||||||
for (x = minX; x <= maxX; x++) {
|
for (y = AVERAGE_MIN_Y; y <= AVERAGE_MAX_Y; y++) {
|
||||||
for (y = minY; y <= maxY; y++) {
|
for (x = AVERAGE_MIN_X; x <= AVERAGE_MAX_X; x++) {
|
||||||
for (z = minZ; z <= maxZ; z++) {
|
for (z = AVERAGE_MIN_Z; z <= AVERAGE_MAX_Z; z++) {
|
||||||
biome = block.getNeighborBlock(x, y, z).getBiome();
|
biome = block.getNeighborBlock(x, y, z).getBiome();
|
||||||
target.add(biome.getWaterColor());
|
target.add(biome.getWaterColor());
|
||||||
}
|
}
|
||||||
@ -157,18 +159,12 @@ public Color getWaterAverageColor(BlockNeighborhood<?> block, Color target) {
|
|||||||
public Color getFoliageAverageColor(BlockNeighborhood<?> block, Color target) {
|
public Color getFoliageAverageColor(BlockNeighborhood<?> block, Color target) {
|
||||||
target.set(0, 0, 0, 0, true);
|
target.set(0, 0, 0, 0, true);
|
||||||
|
|
||||||
int x, y, z,
|
int x, y, z;
|
||||||
minX = - 2,
|
|
||||||
maxX = 2,
|
|
||||||
minY = - 1,
|
|
||||||
maxY = 1,
|
|
||||||
minZ = - 2,
|
|
||||||
maxZ = 2;
|
|
||||||
|
|
||||||
Biome biome;
|
Biome biome;
|
||||||
for (y = minY; y <= maxY; y++) {
|
for (y = AVERAGE_MIN_Y; y <= AVERAGE_MAX_Y; y++) {
|
||||||
for (x = minX; x <= maxX; x++) {
|
for (x = AVERAGE_MIN_X; x <= AVERAGE_MAX_X; x++) {
|
||||||
for (z = minZ; z <= maxZ; z++) {
|
for (z = AVERAGE_MIN_Z; z <= AVERAGE_MAX_Z; z++) {
|
||||||
biome = block.getNeighborBlock(x, y, z).getBiome();
|
biome = block.getNeighborBlock(x, y, z).getBiome();
|
||||||
target.add(getFoliageColor(biome, tempColor));
|
target.add(getFoliageColor(biome, tempColor));
|
||||||
}
|
}
|
||||||
@ -186,18 +182,12 @@ public Color getFoliageColor(Biome biome, Color target) {
|
|||||||
public Color getGrassAverageColor(BlockNeighborhood<?> block, Color target) {
|
public Color getGrassAverageColor(BlockNeighborhood<?> block, Color target) {
|
||||||
target.set(0, 0, 0, 0, true);
|
target.set(0, 0, 0, 0, true);
|
||||||
|
|
||||||
int x, y, z,
|
int x, y, z;
|
||||||
minX = - 2,
|
|
||||||
maxX = 2,
|
|
||||||
minY = - 1,
|
|
||||||
maxY = 1,
|
|
||||||
minZ = - 2,
|
|
||||||
maxZ = 2;
|
|
||||||
|
|
||||||
Biome biome;
|
Biome biome;
|
||||||
for (y = minY; y <= maxY; y++) {
|
for (y = AVERAGE_MIN_Y; y <= AVERAGE_MAX_Y; y++) {
|
||||||
for (x = minX; x <= maxX; x++) {
|
for (x = AVERAGE_MIN_X; x <= AVERAGE_MAX_X; x++) {
|
||||||
for (z = minZ; z <= maxZ; z++) {
|
for (z = AVERAGE_MIN_Z; z <= AVERAGE_MAX_Z; z++) {
|
||||||
biome = block.getNeighborBlock(x, y, z).getBiome();
|
biome = block.getNeighborBlock(x, y, z).getBiome();
|
||||||
target.add(getGrassColor(biome, tempColor));
|
target.add(getGrassColor(biome, tempColor));
|
||||||
}
|
}
|
||||||
|
@ -77,9 +77,6 @@ private static String parsePath(Path filePath) {
|
|||||||
if (filePath.getNameCount() < 4)
|
if (filePath.getNameCount() < 4)
|
||||||
throw new IllegalArgumentException("The provided filePath has less than 4 segments!");
|
throw new IllegalArgumentException("The provided filePath has less than 4 segments!");
|
||||||
|
|
||||||
if (!filePath.getName(0).toString().equalsIgnoreCase("assets"))
|
|
||||||
throw new IllegalArgumentException("The provided filePath doesn't start with 'assets'!");
|
|
||||||
|
|
||||||
String namespace = filePath.getName(1).toString();
|
String namespace = filePath.getName(1).toString();
|
||||||
String path = filePath.subpath(3, filePath.getNameCount()).toString().replace(filePath.getFileSystem().getSeparator(), "/");
|
String path = filePath.subpath(3, filePath.getNameCount()).toString().replace(filePath.getFileSystem().getSeparator(), "/");
|
||||||
|
|
||||||
|
@ -25,22 +25,21 @@
|
|||||||
package de.bluecolored.bluemap.core.resources.adapter;
|
package de.bluecolored.bluemap.core.resources.adapter;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.*;
|
import com.flowpowered.math.vector.*;
|
||||||
|
import com.google.gson.FieldNamingPolicy;
|
||||||
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 {
|
||||||
|
|
||||||
public static final Gson INSTANCE = addAdapter(new GsonBuilder())
|
public static final Gson INSTANCE = addAdapter(new GsonBuilder())
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
.setLenient()
|
.setLenient()
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
@ -60,9 +59,4 @@ public static GsonBuilder addAdapter(GsonBuilder builder) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String nextStringOrBoolean(JsonReader in) throws IOException {
|
|
||||||
if (in.peek() == JsonToken.BOOLEAN) return Boolean.toString(in.nextBoolean());
|
|
||||||
return in.nextString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,10 @@
|
|||||||
package de.bluecolored.bluemap.core.resources.biome.datapack;
|
package de.bluecolored.bluemap.core.resources.biome.datapack;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.world.Biome;
|
import de.bluecolored.bluemap.core.world.Biome;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
@SuppressWarnings("FieldMayBeFinal")
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
@Getter
|
||||||
public class DpBiome {
|
public class DpBiome {
|
||||||
|
|
||||||
private DpBiomeEffects effects = new DpBiomeEffects();
|
private DpBiomeEffects effects = new DpBiomeEffects();
|
||||||
@ -44,16 +46,4 @@ public Biome createBiome(String formatted) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DpBiomeEffects getEffects() {
|
|
||||||
return effects;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTemperature() {
|
|
||||||
return temperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getDownfall() {
|
|
||||||
return downfall;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,24 +26,14 @@
|
|||||||
|
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
import de.bluecolored.bluemap.core.world.Biome;
|
import de.bluecolored.bluemap.core.world.Biome;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
@SuppressWarnings("FieldMayBeFinal")
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
@Getter
|
||||||
public class DpBiomeEffects {
|
public class DpBiomeEffects {
|
||||||
|
|
||||||
private Color water_color = Biome.DEFAULT.getWaterColor();
|
private Color waterColor = Biome.DEFAULT.getWaterColor();
|
||||||
private Color foliage_color = Biome.DEFAULT.getOverlayFoliageColor();
|
private Color foliageColor = Biome.DEFAULT.getOverlayFoliageColor();
|
||||||
private Color grass_color = Biome.DEFAULT.getOverlayGrassColor();
|
private Color grassColor = Biome.DEFAULT.getOverlayGrassColor();
|
||||||
|
|
||||||
public Color getWaterColor() {
|
|
||||||
return water_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color getFoliageColor() {
|
|
||||||
return foliage_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color getGrassColor() {
|
|
||||||
return grass_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
package de.bluecolored.bluemap.core.resources.datapack;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||||
|
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||||
|
import de.bluecolored.bluemap.core.resources.datapack.dimension.DimensionTypeData;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import de.bluecolored.bluemap.core.world.DimensionType;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class DataPack {
|
||||||
|
|
||||||
|
public static final Key DIMENSION_OVERWORLD = new Key("minecraft", "overworld");
|
||||||
|
public static final Key DIMENSION_THE_NETHER = new Key("minecraft", "the_nether");
|
||||||
|
public static final Key DIMENSION_THE_END = new Key("minecraft", "the_end");
|
||||||
|
|
||||||
|
public static final Key DIMENSION_TYPE_OVERWORLD = new Key("minecraft", "overworld");
|
||||||
|
public static final Key DIMENSION_TYPE_OVERWORLD_CAVES = new Key("minecraft", "overworld_caves");
|
||||||
|
public static final Key DIMENSION_TYPE_THE_NETHER = new Key("minecraft", "the_nether");
|
||||||
|
public static final Key DIMENSION_TYPE_THE_END = new Key("minecraft", "the_end");
|
||||||
|
|
||||||
|
private final Map<Key, DimensionType> dimensionTypes = new HashMap<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public DimensionType getDimensionType(Key key) {
|
||||||
|
return dimensionTypes.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(Path root) throws InterruptedException {
|
||||||
|
Logger.global.logDebug("Loading datapack from: " + root + " ...");
|
||||||
|
loadPath(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadPath(Path root) throws InterruptedException {
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
if (!Files.isDirectory(root)) {
|
||||||
|
try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) {
|
||||||
|
for (Path fsRoot : fileSystem.getRootDirectories()) {
|
||||||
|
if (!Files.isDirectory(fsRoot)) continue;
|
||||||
|
loadPath(fsRoot);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.global.logDebug("Failed to read '" + root + "': " + ex);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list(root.resolve("data"))
|
||||||
|
.map(path -> path.resolve("dimension_type"))
|
||||||
|
.filter(Files::isDirectory)
|
||||||
|
.flatMap(DataPack::walk)
|
||||||
|
.filter(path -> path.getFileName().toString().endsWith(".json"))
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.forEach(file -> loadResource(root, file, () -> {
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||||
|
return ResourcesGson.INSTANCE.fromJson(reader, DimensionTypeData.class);
|
||||||
|
}
|
||||||
|
}, dimensionTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void loadResource(Path root, Path file, Loader<T> loader, Map<Key, T> resultMap) {
|
||||||
|
try {
|
||||||
|
ResourcePath<T> resourcePath = new ResourcePath<>(root.relativize(file));
|
||||||
|
if (resultMap.containsKey(resourcePath)) return; // don't load already present resources
|
||||||
|
|
||||||
|
T resource = loader.load();
|
||||||
|
if (resource == null) return; // don't load missing resources
|
||||||
|
|
||||||
|
resourcePath.setResource(resource);
|
||||||
|
resultMap.put(resourcePath, resource);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bake() {
|
||||||
|
dimensionTypes.putIfAbsent(DIMENSION_TYPE_OVERWORLD, DimensionType.OVERWORLD);
|
||||||
|
dimensionTypes.putIfAbsent(DIMENSION_TYPE_OVERWORLD_CAVES, DimensionType.OVERWORLD_CAVES);
|
||||||
|
dimensionTypes.putIfAbsent(DIMENSION_TYPE_THE_NETHER, DimensionType.NETHER);
|
||||||
|
dimensionTypes.putIfAbsent(DIMENSION_TYPE_THE_END, DimensionType.END);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Path> list(Path root) {
|
||||||
|
if (!Files.isDirectory(root)) return Stream.empty();
|
||||||
|
try {
|
||||||
|
return Files.list(root);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CompletionException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Path> walk(Path root) {
|
||||||
|
if (!Files.exists(root)) return Stream.empty();
|
||||||
|
if (Files.isRegularFile(root)) return Stream.of(root);
|
||||||
|
try {
|
||||||
|
return Files.walk(root);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CompletionException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Loader<T> {
|
||||||
|
T load() throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package de.bluecolored.bluemap.core.resources.datapack.dimension;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
|
import de.bluecolored.bluemap.core.world.DimensionType;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@DebugDump
|
||||||
|
public class DimensionTypeData implements DimensionType {
|
||||||
|
|
||||||
|
private boolean natural;
|
||||||
|
@Accessors(fluent = true) private boolean hasSkylight;
|
||||||
|
@Accessors(fluent = true) private boolean hasCeiling;
|
||||||
|
private float ambientLight;
|
||||||
|
private int minY;
|
||||||
|
private int height;
|
||||||
|
private Long fixedTime;
|
||||||
|
private double coordinateScale;
|
||||||
|
|
||||||
|
}
|
@ -183,20 +183,26 @@ private BlockProperties loadBlockProperties(de.bluecolored.bluemap.core.world.Bl
|
|||||||
return props.build();
|
return props.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void loadResources(Iterable<Path> roots) throws IOException {
|
public synchronized void loadResources(Iterable<Path> roots) throws IOException, InterruptedException {
|
||||||
Logger.global.logInfo("Loading resources...");
|
Logger.global.logInfo("Loading resources...");
|
||||||
|
|
||||||
for (Path root : roots) {
|
for (Path root : roots) {
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
Logger.global.logDebug("Loading resources from: " + root + " ...");
|
Logger.global.logDebug("Loading resources from: " + root + " ...");
|
||||||
loadResourcePath(root, this::loadResources);
|
loadResourcePath(root, this::loadResources);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.global.logInfo("Loading textures...");
|
Logger.global.logInfo("Loading textures...");
|
||||||
for (Path root : roots) {
|
for (Path root : roots) {
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
Logger.global.logDebug("Loading textures from: " + root + " ...");
|
Logger.global.logDebug("Loading textures from: " + root + " ...");
|
||||||
loadResourcePath(root, this::loadTextures);
|
loadResourcePath(root, this::loadTextures);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
Logger.global.logInfo("Baking resources...");
|
Logger.global.logInfo("Baking resources...");
|
||||||
bake();
|
bake();
|
||||||
|
|
||||||
@ -204,7 +210,8 @@ public synchronized void loadResources(Iterable<Path> roots) throws IOException
|
|||||||
Logger.global.logInfo("Resources loaded.");
|
Logger.global.logInfo("Resources loaded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadResourcePath(Path root, PathLoader resourceLoader) throws IOException {
|
private void loadResourcePath(Path root, PathLoader resourceLoader) throws IOException, InterruptedException {
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
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()) {
|
||||||
@ -299,6 +306,7 @@ private void loadResources(Path root) throws IOException {
|
|||||||
}, BlueMap.THREAD_POOL),
|
}, BlueMap.THREAD_POOL),
|
||||||
|
|
||||||
// load biome configs
|
// load biome configs
|
||||||
|
// TODO: move this to datapacks?
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
list(root.resolve("assets"))
|
list(root.resolve("assets"))
|
||||||
.map(path -> path.resolve("biomes.json"))
|
.map(path -> path.resolve("biomes.json"))
|
||||||
@ -386,7 +394,7 @@ private void loadTextures(Path root) throws IOException {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bake() throws IOException {
|
private void bake() throws IOException, InterruptedException {
|
||||||
|
|
||||||
// fill path maps
|
// fill path maps
|
||||||
blockStates.keySet().forEach(path -> blockStatePaths.put(path.getFormatted(), path));
|
blockStates.keySet().forEach(path -> blockStatePaths.put(path.getFormatted(), path));
|
||||||
@ -398,11 +406,15 @@ private void bake() throws IOException {
|
|||||||
model.optimize(this);
|
model.optimize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
// apply model parents
|
// apply model parents
|
||||||
for (BlockModel model : blockModels.values()) {
|
for (BlockModel model : blockModels.values()) {
|
||||||
model.applyParent(this);
|
model.applyParent(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Thread.interrupted()) throw new InterruptedException();
|
||||||
|
|
||||||
// calculate model properties
|
// calculate model properties
|
||||||
for (BlockModel model : blockModels.values()) {
|
for (BlockModel model : blockModels.values()) {
|
||||||
model.calculateProperties(this);
|
model.calculateProperties(this);
|
||||||
|
@ -27,9 +27,9 @@
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
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 com.google.gson.stream.JsonToken;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.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;
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ public BlockStateCondition readCondition(JsonReader in) throws IOException {
|
|||||||
andConditions.add(
|
andConditions.add(
|
||||||
BlockStateCondition.and(andArray.toArray(new BlockStateCondition[0])));
|
BlockStateCondition.and(andArray.toArray(new BlockStateCondition[0])));
|
||||||
} else {
|
} else {
|
||||||
String[] values = StringUtils.split(ResourcesGson.nextStringOrBoolean(in), '|');
|
String[] values = StringUtils.split(nextStringOrBoolean(in), '|');
|
||||||
andConditions.add(BlockStateCondition.property(name, values));
|
andConditions.add(BlockStateCondition.property(name, values));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,6 +134,11 @@ public BlockStateCondition readCondition(JsonReader in) throws IOException {
|
|||||||
return BlockStateCondition.and(andConditions.toArray(new BlockStateCondition[0]));
|
return BlockStateCondition.and(andConditions.toArray(new BlockStateCondition[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String nextStringOrBoolean(JsonReader in) throws IOException {
|
||||||
|
if (in.peek() == JsonToken.BOOLEAN) return Boolean.toString(in.nextBoolean());
|
||||||
|
return in.nextString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,16 +24,16 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.storage;
|
package de.bluecolored.bluemap.core.storage;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class CompressedInputStream extends InputStream {
|
public class CompressedInputStream extends FilterInputStream {
|
||||||
|
|
||||||
private final InputStream in;
|
|
||||||
private final Compression compression;
|
private final Compression compression;
|
||||||
|
|
||||||
public CompressedInputStream(InputStream in, Compression compression) {
|
public CompressedInputStream(InputStream in, Compression compression) {
|
||||||
this.in = in;
|
super(in);
|
||||||
this.compression = compression;
|
this.compression = compression;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,29 +45,4 @@ public Compression getCompression() {
|
|||||||
return compression;
|
return compression;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
return in.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
return in.read(b, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int available() throws IOException {
|
|
||||||
return in.available();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() throws IOException {
|
|
||||||
in.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,22 +26,25 @@
|
|||||||
|
|
||||||
import io.airlift.compress.zstd.ZstdInputStream;
|
import io.airlift.compress.zstd.ZstdInputStream;
|
||||||
import io.airlift.compress.zstd.ZstdOutputStream;
|
import io.airlift.compress.zstd.ZstdOutputStream;
|
||||||
|
import net.jpountz.lz4.LZ4FrameInputStream;
|
||||||
|
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.zip.DeflaterInputStream;
|
|
||||||
import java.util.zip.DeflaterOutputStream;
|
import java.util.zip.DeflaterOutputStream;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
public enum Compression {
|
public enum Compression {
|
||||||
|
|
||||||
NONE("none", "", out -> out, in -> in),
|
NONE("none", "", out -> out, in -> in),
|
||||||
GZIP("gzip", ".gz", GZIPOutputStream::new, GZIPInputStream::new),
|
GZIP("gzip", ".gz", GZIPOutputStream::new, GZIPInputStream::new),
|
||||||
DEFLATE("deflate", ".deflate", DeflaterOutputStream::new, DeflaterInputStream::new),
|
DEFLATE("deflate", ".deflate", DeflaterOutputStream::new, InflaterInputStream::new),
|
||||||
ZSTD("zstd", ".zst", ZstdOutputStream::new, ZstdInputStream::new);
|
ZSTD("zstd", ".zst", ZstdOutputStream::new, ZstdInputStream::new),
|
||||||
|
LZ4("lz4", ".lz4", LZ4FrameOutputStream::new, LZ4FrameInputStream::new);
|
||||||
|
|
||||||
private final String typeId;
|
private final String typeId;
|
||||||
private final String fileSuffix;
|
private final String fileSuffix;
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
import de.bluecolored.bluemap.core.storage.Compression;
|
import de.bluecolored.bluemap.core.storage.Compression;
|
||||||
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
||||||
import de.bluecolored.bluemap.core.storage.sql.dialect.PostgresDialect;
|
import de.bluecolored.bluemap.core.storage.sql.dialect.PostgresDialect;
|
||||||
import de.bluecolored.bluemap.core.util.WrappedOutputStream;
|
import de.bluecolored.bluemap.core.util.OnCloseOutputStream;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
@ -51,7 +51,7 @@ public PostgreSQLStorage(Dialect dialect, SQLStorageSettings config) throws Malf
|
|||||||
public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||||
return new WrappedOutputStream(compression.compress(byteOut), () -> {
|
return new OnCloseOutputStream(compression.compress(byteOut), () -> {
|
||||||
int mapFK = getMapFK(mapId);
|
int mapFK = getMapFK(mapId);
|
||||||
int tileCompressionFK = getMapTileCompressionFK(compression);
|
int tileCompressionFK = getMapTileCompressionFK(compression);
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IO
|
|||||||
@Override
|
@Override
|
||||||
public OutputStream writeMeta(String mapId, String name) {
|
public OutputStream writeMeta(String mapId, String name) {
|
||||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||||
return new WrappedOutputStream(byteOut, () -> {
|
return new OnCloseOutputStream(byteOut, () -> {
|
||||||
int mapFK = getMapFK(mapId);
|
int mapFK = getMapFK(mapId);
|
||||||
recoveringConnection(connection -> {
|
recoveringConnection(connection -> {
|
||||||
executeUpdate(connection, this.dialect.writeMeta(),
|
executeUpdate(connection, this.dialect.writeMeta(),
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
import de.bluecolored.bluemap.core.storage.*;
|
import de.bluecolored.bluemap.core.storage.*;
|
||||||
import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
|
import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
|
||||||
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
||||||
import de.bluecolored.bluemap.core.util.WrappedOutputStream;
|
import de.bluecolored.bluemap.core.util.OnCloseOutputStream;
|
||||||
import org.apache.commons.dbcp2.*;
|
import org.apache.commons.dbcp2.*;
|
||||||
import org.apache.commons.pool2.ObjectPool;
|
import org.apache.commons.pool2.ObjectPool;
|
||||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||||
@ -108,7 +108,7 @@ public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IO
|
|||||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||||
|
|
||||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||||
return new WrappedOutputStream(compression.compress(byteOut), () -> {
|
return new OnCloseOutputStream(compression.compress(byteOut), () -> {
|
||||||
int mapFK = getMapFK(mapId);
|
int mapFK = getMapFK(mapId);
|
||||||
int tileCompressionFK = getMapTileCompressionFK(compression);
|
int tileCompressionFK = getMapTileCompressionFK(compression);
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ public void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOExcepti
|
|||||||
@Override
|
@Override
|
||||||
public OutputStream writeMeta(String mapId, String name) {
|
public OutputStream writeMeta(String mapId, String name) {
|
||||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||||
return new WrappedOutputStream(byteOut, () -> {
|
return new OnCloseOutputStream(byteOut, () -> {
|
||||||
int mapFK = getMapFK(mapId);
|
int mapFK = getMapFK(mapId);
|
||||||
|
|
||||||
recoveringConnection(connection -> {
|
recoveringConnection(connection -> {
|
||||||
|
@ -40,7 +40,7 @@ public static OutputStream createFilepartOutputStream(final Path file) throws IO
|
|||||||
final Path partFile = getPartFile(file);
|
final Path partFile = getPartFile(file);
|
||||||
FileHelper.createDirectories(partFile.getParent());
|
FileHelper.createDirectories(partFile.getParent());
|
||||||
OutputStream os = Files.newOutputStream(partFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
|
OutputStream os = Files.newOutputStream(partFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
|
||||||
return new WrappedOutputStream(os, () -> {
|
return new OnCloseOutputStream(os, () -> {
|
||||||
if (!Files.exists(partFile)) return;
|
if (!Files.exists(partFile)) return;
|
||||||
FileHelper.createDirectories(file.getParent());
|
FileHelper.createDirectories(file.getParent());
|
||||||
FileHelper.move(partFile, file);
|
FileHelper.move(partFile, file);
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.world;
|
package de.bluecolored.bluemap.core.util;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
|
|
@ -26,10 +26,15 @@
|
|||||||
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
|
|
||||||
@DebugDump
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
public class Key {
|
|
||||||
|
|
||||||
private static final String MINECRAFT_NAMESPACE = "minecraft";
|
@DebugDump
|
||||||
|
public class Key implements Keyed {
|
||||||
|
|
||||||
|
private static final ConcurrentHashMap<String, String> STRING_INTERN_POOL = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static final String MINECRAFT_NAMESPACE = "minecraft";
|
||||||
|
public static final String BLUEMAP_NAMESPACE = "bluemap";
|
||||||
|
|
||||||
private final String namespace;
|
private final String namespace;
|
||||||
private final String value;
|
private final String value;
|
||||||
@ -44,15 +49,15 @@ public Key(String formatted) {
|
|||||||
value = formatted.substring(namespaceSeparator + 1);
|
value = formatted.substring(namespaceSeparator + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.namespace = namespace.intern();
|
this.namespace = intern(namespace);
|
||||||
this.value = value.intern();
|
this.value = intern(value);
|
||||||
this.formatted = (this.namespace + ":" + this.value).intern();
|
this.formatted = intern(this.namespace + ":" + this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Key(String namespace, String value) {
|
public Key(String namespace, String value) {
|
||||||
this.namespace = namespace.intern();
|
this.namespace = intern(namespace);
|
||||||
this.value = value.intern();
|
this.value = intern(value);
|
||||||
this.formatted = (this.namespace + ":" + this.value).intern();
|
this.formatted = intern(this.namespace + ":" + this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNamespace() {
|
public String getNamespace() {
|
||||||
@ -67,22 +72,52 @@ public String getFormatted() {
|
|||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key getKey() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("StringEquality")
|
@SuppressWarnings("StringEquality")
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
Key that = (Key) o;
|
Key that = (Key) o;
|
||||||
return getFormatted() == that.getFormatted();
|
return formatted == that.formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getFormatted().hashCode();
|
return formatted.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Key parse(String formatted) {
|
||||||
|
return new Key(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Key parse(String formatted, String defaultNamespace) {
|
||||||
|
String namespace = defaultNamespace;
|
||||||
|
String value = formatted;
|
||||||
|
int namespaceSeparator = formatted.indexOf(':');
|
||||||
|
if (namespaceSeparator > 0) {
|
||||||
|
namespace = formatted.substring(0, namespaceSeparator);
|
||||||
|
value = formatted.substring(namespaceSeparator + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Key(namespace, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using our own function instead of {@link String#intern()} since the ConcurrentHashMap is much faster.
|
||||||
|
*/
|
||||||
|
protected static String intern(String string) {
|
||||||
|
String interned = STRING_INTERN_POOL.putIfAbsent(string, string);
|
||||||
|
return interned != null ? interned : string;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.bluecolored.bluemap.core.util;
|
||||||
|
|
||||||
|
public interface Keyed {
|
||||||
|
|
||||||
|
Key getKey();
|
||||||
|
|
||||||
|
}
|
@ -24,34 +24,19 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.util;
|
package de.bluecolored.bluemap.core.util;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class WrappedInputStream extends InputStream {
|
public class OnCloseInputStream extends FilterInputStream {
|
||||||
|
|
||||||
private final InputStream in;
|
|
||||||
private final AutoCloseable onClose;
|
private final AutoCloseable onClose;
|
||||||
|
|
||||||
public WrappedInputStream(InputStream in, AutoCloseable onClose) {
|
public OnCloseInputStream(InputStream in, AutoCloseable onClose) {
|
||||||
this.in = in;
|
super(in);
|
||||||
this.onClose = onClose;
|
this.onClose = onClose;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
return in.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b) throws IOException {
|
|
||||||
return in.read(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
return in.read(b, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
IOException ioExcetion = null;
|
IOException ioExcetion = null;
|
@ -24,39 +24,19 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.util;
|
package de.bluecolored.bluemap.core.util;
|
||||||
|
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class WrappedOutputStream extends OutputStream {
|
public class OnCloseOutputStream extends FilterOutputStream {
|
||||||
|
|
||||||
private final OutputStream out;
|
|
||||||
private final AutoCloseable onClose;
|
private final AutoCloseable onClose;
|
||||||
|
|
||||||
public WrappedOutputStream(OutputStream out, AutoCloseable onClose) {
|
public OnCloseOutputStream(OutputStream out, AutoCloseable onClose) {
|
||||||
this.out = out;
|
super(out);
|
||||||
this.onClose = onClose;
|
this.onClose = onClose;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(int b) throws IOException {
|
|
||||||
out.write(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b) throws IOException {
|
|
||||||
out.write(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b, int off, int len) throws IOException {
|
|
||||||
out.write(b, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() throws IOException {
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
IOException ioExcetion = null;
|
IOException ioExcetion = null;
|
@ -0,0 +1,53 @@
|
|||||||
|
package de.bluecolored.bluemap.core.util;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class Registry<T extends Keyed> {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Key, T> entries;
|
||||||
|
|
||||||
|
public Registry() {
|
||||||
|
this.entries = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new entry, only if there is no entry with the same key registered already.
|
||||||
|
* Does nothing otherwise.
|
||||||
|
* @param entry The new entry to be added to this registry
|
||||||
|
* @return true if the entry has been added, false if there is already an entry with the same key registered
|
||||||
|
*/
|
||||||
|
public boolean register(T entry) {
|
||||||
|
Objects.requireNonNull(entry, "registry entry can not be null");
|
||||||
|
return entries.putIfAbsent(entry.getKey(), entry) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an entry from this registry for a key.
|
||||||
|
* @param key The key to search for
|
||||||
|
* @return The entry with the key, or null if there is no entry for this key
|
||||||
|
*/
|
||||||
|
public @Nullable T get(Key key) {
|
||||||
|
return entries.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an unmodifiable set of all keys this registry contains entries for
|
||||||
|
*/
|
||||||
|
public Set<Key> keys() {
|
||||||
|
return Collections.unmodifiableSet(entries.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an unmodifiable collection of entries in this registry
|
||||||
|
*/
|
||||||
|
public Collection<T> values() {
|
||||||
|
return Collections.unmodifiableCollection(entries.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -197,8 +197,8 @@ public static final class Property implements Comparable<Property> {
|
|||||||
private final String key, value;
|
private final String key, value;
|
||||||
|
|
||||||
public Property(String key, String value) {
|
public Property(String key, String value) {
|
||||||
this.key = key.intern();
|
this.key = intern(key);
|
||||||
this.value = value.intern();
|
this.value = intern(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("StringEquality")
|
@SuppressWarnings("StringEquality")
|
||||||
|
@ -26,22 +26,50 @@
|
|||||||
|
|
||||||
public interface Chunk {
|
public interface Chunk {
|
||||||
|
|
||||||
boolean isGenerated();
|
Chunk EMPTY_CHUNK = new Chunk() {};
|
||||||
|
|
||||||
long getInhabitedTime();
|
default boolean isGenerated() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
BlockState getBlockState(int x, int y, int z);
|
default boolean hasLightData() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
LightData getLightData(int x, int y, int z, LightData target);
|
default long getInhabitedTime() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
String getBiome(int x, int y, int z);
|
default BlockState getBlockState(int x, int y, int z) {
|
||||||
|
return BlockState.AIR;
|
||||||
|
}
|
||||||
|
|
||||||
int getMaxY(int x, int z);
|
default LightData getLightData(int x, int y, int z, LightData target) {
|
||||||
|
return target.set(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
int getMinY(int x, int z);
|
default String getBiome(int x, int y, int z) {
|
||||||
|
return Biome.DEFAULT.getFormatted();
|
||||||
|
}
|
||||||
|
|
||||||
int getWorldSurfaceY(int x, int z);
|
default int getMaxY(int x, int z) {
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
|
||||||
int getOceanFloorY(int x, int z);
|
default int getMinY(int x, int z) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean hasWorldSurfaceHeights() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default int getWorldSurfaceY(int x, int z) { return 0; }
|
||||||
|
|
||||||
|
default boolean hasOceanFloorHeights() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default int getOceanFloorY(int x, int z) { return 0; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ChunkConsumer {
|
||||||
|
|
||||||
|
default boolean filter(int chunkX, int chunkZ, long lastModified) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void accept(int chunkX, int chunkZ, Chunk chunk);
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface ListOnly extends ChunkConsumer {
|
||||||
|
|
||||||
|
void accept(int chunkX, int chunkZ, long lastModified);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean filter(int chunkX, int chunkZ, long lastModified) {
|
||||||
|
accept(chunkX, chunkZ, lastModified);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void accept(int chunkX, int chunkZ, Chunk chunk) {
|
||||||
|
throw new IllegalStateException("Should never be called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
public interface DimensionType {
|
||||||
|
|
||||||
|
DimensionType OVERWORLD = new Builtin(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
0f,
|
||||||
|
-64,
|
||||||
|
384,
|
||||||
|
null,
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
DimensionType OVERWORLD_CAVES = new Builtin(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
0,
|
||||||
|
-64,
|
||||||
|
384,
|
||||||
|
null,
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
DimensionType NETHER = new Builtin(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
0.1f,
|
||||||
|
0,
|
||||||
|
256,
|
||||||
|
6000L,
|
||||||
|
8.0
|
||||||
|
);
|
||||||
|
DimensionType END = new Builtin(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
256,
|
||||||
|
18000L,
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
|
||||||
|
boolean isNatural();
|
||||||
|
|
||||||
|
boolean hasSkylight();
|
||||||
|
|
||||||
|
boolean hasCeiling();
|
||||||
|
|
||||||
|
float getAmbientLight();
|
||||||
|
|
||||||
|
int getMinY();
|
||||||
|
|
||||||
|
int getHeight();
|
||||||
|
|
||||||
|
Long getFixedTime();
|
||||||
|
|
||||||
|
double getCoordinateScale();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
class Builtin implements DimensionType {
|
||||||
|
|
||||||
|
private final boolean natural;
|
||||||
|
@Accessors(fluent = true) private final boolean hasSkylight;
|
||||||
|
@Accessors(fluent = true) private final boolean hasCeiling;
|
||||||
|
private final float ambientLight;
|
||||||
|
private final int minY;
|
||||||
|
private final int height;
|
||||||
|
private final Long fixedTime;
|
||||||
|
private final double coordinateScale;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.world;
|
|
||||||
|
|
||||||
public class EmptyChunk implements Chunk {
|
|
||||||
|
|
||||||
public static final Chunk INSTANCE = new EmptyChunk();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isGenerated() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getInhabitedTime() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BlockState getBlockState(int x, int y, int z) {
|
|
||||||
return BlockState.AIR;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
|
||||||
return target.set(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getBiome(int x, int y, int z) {
|
|
||||||
return Biome.DEFAULT.getFormatted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxY(int x, int z) {
|
|
||||||
return 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinY(int x, int z) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWorldSurfaceY(int x, int z) { return 0; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOceanFloorY(int x, int z) { return 0; }
|
|
||||||
|
|
||||||
}
|
|
@ -24,34 +24,41 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.world;
|
package de.bluecolored.bluemap.core.world;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public interface Region {
|
public interface Region {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a collection of all generated chunks.<br>
|
* Directly loads and returns the specified chunk.<br>
|
||||||
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
|
* (implementations should consider overriding this method for a faster implementation)
|
||||||
*/
|
*/
|
||||||
default Collection<Vector2i> listChunks(){
|
default Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
|
||||||
return listChunks(0);
|
class SingleChunkConsumer implements ChunkConsumer {
|
||||||
|
private Chunk foundChunk = Chunk.EMPTY_CHUNK;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean filter(int x, int z, long lastModified) {
|
||||||
|
return x == chunkX && z == chunkZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(int chunkX, int chunkZ, Chunk chunk) {
|
||||||
|
this.foundChunk = chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleChunkConsumer singleChunkConsumer = new SingleChunkConsumer();
|
||||||
|
iterateAllChunks(singleChunkConsumer);
|
||||||
|
return singleChunkConsumer.foundChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a collection of all chunks that have been modified at or after the specified timestamp.<br>
|
* Iterates over all chunks in this region and first calls {@link ChunkConsumer#filter(int, int, long)}.<br>
|
||||||
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
|
* And if (any only if) that method returned <code>true</code>, the chunk will be loaded and {@link ChunkConsumer#accept(int, int, Chunk)}
|
||||||
|
* will be called with the loaded chunk.
|
||||||
|
* @param consumer the consumer choosing which chunks to load and accepting them
|
||||||
|
* @throws IOException if an IOException occurred trying to read the region
|
||||||
*/
|
*/
|
||||||
Collection<Vector2i> listChunks(long modifiedSince);
|
void iterateAllChunks(ChunkConsumer consumer) throws IOException;
|
||||||
|
|
||||||
default Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
|
|
||||||
return loadChunk(chunkX, chunkZ, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException;
|
|
||||||
|
|
||||||
Path getRegionFile();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,29 +26,25 @@
|
|||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
import com.flowpowered.math.vector.Vector3i;
|
import com.flowpowered.math.vector.Vector3i;
|
||||||
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a World on the Server<br>
|
* Represents a World on the Server.<br>
|
||||||
|
* This is usually one of the dimensions of a level.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* <i>The implementation of this class has to be thread-save!</i><br>
|
* <i>The implementation of this class has to be thread-save!</i><br>
|
||||||
*/
|
*/
|
||||||
public interface World {
|
public interface World {
|
||||||
|
|
||||||
Path getSaveFolder();
|
String getId();
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
int getSkyLight();
|
|
||||||
|
|
||||||
Vector3i getSpawnPoint();
|
Vector3i getSpawnPoint();
|
||||||
|
|
||||||
int getMaxY(int x, int z);
|
DimensionType getDimensionType();
|
||||||
|
|
||||||
int getMinY(int x, int z);
|
|
||||||
|
|
||||||
Grid getChunkGrid();
|
Grid getChunkGrid();
|
||||||
|
|
||||||
@ -57,7 +53,7 @@ public interface World {
|
|||||||
/**
|
/**
|
||||||
* Returns the {@link Chunk} on the specified block-position
|
* Returns the {@link Chunk} on the specified block-position
|
||||||
*/
|
*/
|
||||||
Chunk getChunkAtBlock(int x, int y, int z);
|
Chunk getChunkAtBlock(int x, int z);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link Chunk} on the specified chunk-position
|
* Returns the {@link Chunk} on the specified chunk-position
|
||||||
@ -75,6 +71,11 @@ public interface World {
|
|||||||
*/
|
*/
|
||||||
Collection<Vector2i> listRegions();
|
Collection<Vector2i> listRegions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all chunks from the specified region into the chunk cache (if there is a cache)
|
||||||
|
*/
|
||||||
|
void preloadRegionChunks(int x, int z);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk
|
* Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk
|
||||||
*/
|
*/
|
||||||
|
@ -22,7 +22,12 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.world;
|
package de.bluecolored.bluemap.core.world.block;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
|
import de.bluecolored.bluemap.core.world.Chunk;
|
||||||
|
import de.bluecolored.bluemap.core.world.LightData;
|
||||||
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
public class Block<T extends Block<T>> {
|
public class Block<T extends Block<T>> {
|
||||||
|
|
||||||
@ -98,22 +103,6 @@ public T copy(Block<?> source) {
|
|||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* copy with offset
|
|
||||||
*/
|
|
||||||
public T copy(Block<?> source, int dx, int dy, int dz) {
|
|
||||||
this.world = source.world;
|
|
||||||
this.x = source.x + dx;
|
|
||||||
this.y = source.y + dy;
|
|
||||||
this.z = source.z + dz;
|
|
||||||
|
|
||||||
this.chunk = null;
|
|
||||||
|
|
||||||
reset();
|
|
||||||
|
|
||||||
return self();
|
|
||||||
}
|
|
||||||
|
|
||||||
public World getWorld() {
|
public World getWorld() {
|
||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
@ -131,7 +120,7 @@ public int getZ() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Chunk getChunk() {
|
public Chunk getChunk() {
|
||||||
if (chunk == null) chunk = world.getChunkAtBlock(x, y, z);
|
if (chunk == null) chunk = world.getChunkAtBlock(x, z);
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
@ -22,10 +22,11 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.world;
|
package de.bluecolored.bluemap.core.world.block;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||||
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
public class BlockNeighborhood<T extends BlockNeighborhood<T>> extends ExtendedBlock<T> {
|
public class BlockNeighborhood<T extends BlockNeighborhood<T>> extends ExtendedBlock<T> {
|
||||||
|
|
||||||
@ -52,6 +53,19 @@ public BlockNeighborhood(ResourcePack resourcePack, RenderSettings renderSetting
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T set(int x, int y, int z) {
|
||||||
|
return copy(getBlock(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T set(World world, int x, int y, int z) {
|
||||||
|
if (getWorld() == world)
|
||||||
|
return copy(getBlock(x, y, z));
|
||||||
|
else
|
||||||
|
return super.set(world, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void reset() {
|
protected void reset() {
|
||||||
super.reset();
|
super.reset();
|
||||||
@ -67,25 +81,28 @@ private void init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ExtendedBlock<?> getNeighborBlock(int dx, int dy, int dz) {
|
public ExtendedBlock<?> getNeighborBlock(int dx, int dy, int dz) {
|
||||||
int i = neighborIndex(dx, dy, dz);
|
return getBlock(
|
||||||
if (i == thisIndex()) return this;
|
|
||||||
return neighborhood[i].set(
|
|
||||||
getWorld(),
|
|
||||||
getX() + dx,
|
getX() + dx,
|
||||||
getY() + dy,
|
getY() + dy,
|
||||||
getZ() + dz
|
getZ() + dz
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ExtendedBlock<?> getBlock(int x, int y, int z) {
|
||||||
|
int i = index(x, y, z);
|
||||||
|
if (i == thisIndex()) return this;
|
||||||
|
return neighborhood[i].set(getWorld(), x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
private int thisIndex() {
|
private int thisIndex() {
|
||||||
if (thisIndex == -1) thisIndex = neighborIndex(0, 0, 0);
|
if (thisIndex == -1) thisIndex = index(getX(), getY(), getZ());
|
||||||
return thisIndex;
|
return thisIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int neighborIndex(int dx, int dy, int dz) {
|
private int index(int x, int y, int z) {
|
||||||
return ((getX() + dx) & DIAMETER_MASK) * DIAMETER_SQUARED +
|
return (x & DIAMETER_MASK) * DIAMETER_SQUARED +
|
||||||
((getY() + dy) & DIAMETER_MASK) * DIAMETER +
|
(y & DIAMETER_MASK) * DIAMETER +
|
||||||
((getZ() + dz) & DIAMETER_MASK);
|
(z & DIAMETER_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -22,20 +22,23 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.world;
|
package de.bluecolored.bluemap.core.world.block;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||||
|
import de.bluecolored.bluemap.core.world.*;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
|
public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
|
||||||
private final ResourcePack resourcePack;
|
private final ResourcePack resourcePack;
|
||||||
private final RenderSettings renderSettings;
|
private final RenderSettings renderSettings;
|
||||||
|
|
||||||
private BlockProperties properties;
|
private BlockProperties properties;
|
||||||
private Biome biome;
|
private Biome biome;
|
||||||
|
|
||||||
private boolean insideRenderBoundsCalculated, insideRenderBounds;
|
private boolean insideRenderBoundsCalculated, insideRenderBounds;
|
||||||
|
private boolean isCaveCalculated, isCave;
|
||||||
|
|
||||||
public ExtendedBlock(ResourcePack resourcePack, RenderSettings renderSettings, World world, int x, int y, int z) {
|
public ExtendedBlock(ResourcePack resourcePack, RenderSettings renderSettings, World world, int x, int y, int z) {
|
||||||
super(world, x, y, z);
|
super(world, x, y, z);
|
||||||
@ -51,6 +54,22 @@ protected void reset() {
|
|||||||
this.biome = null;
|
this.biome = null;
|
||||||
|
|
||||||
this.insideRenderBoundsCalculated = false;
|
this.insideRenderBoundsCalculated = false;
|
||||||
|
this.isCaveCalculated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T copy(ExtendedBlock<?> source) {
|
||||||
|
super.copy(source);
|
||||||
|
|
||||||
|
this.properties = source.properties;
|
||||||
|
this.biome = source.biome;
|
||||||
|
|
||||||
|
this.insideRenderBoundsCalculated = source.insideRenderBoundsCalculated;
|
||||||
|
this.insideRenderBounds = source.insideRenderBounds;
|
||||||
|
|
||||||
|
this.isCaveCalculated = source.isCaveCalculated;
|
||||||
|
this.isCave = source.isCave;
|
||||||
|
|
||||||
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -62,7 +81,7 @@ public BlockState getBlockState() {
|
|||||||
@Override
|
@Override
|
||||||
public LightData getLightData() {
|
public LightData getLightData() {
|
||||||
LightData ld = super.getLightData();
|
LightData ld = super.getLightData();
|
||||||
if (renderSettings.isRenderEdges() && !isInsideRenderBounds()) ld.set(getWorld().getSkyLight(), ld.getBlockLight());
|
if (renderSettings.isRenderEdges() && !isInsideRenderBounds()) ld.set(getWorld().getDimensionType().hasSkylight() ? 16 : 0, ld.getBlockLight());
|
||||||
return ld;
|
return ld;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +109,20 @@ public boolean isInsideRenderBounds() {
|
|||||||
return insideRenderBounds;
|
return insideRenderBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRemoveIfCave() {
|
||||||
|
if (!isCaveCalculated) {
|
||||||
|
isCave = getY() < renderSettings.getRemoveCavesBelowY() &&
|
||||||
|
(
|
||||||
|
!getChunk().hasOceanFloorHeights() ||
|
||||||
|
getY() < getChunk().getOceanFloorY(getX(), getZ()) +
|
||||||
|
renderSettings.getCaveDetectionOceanFloor()
|
||||||
|
);
|
||||||
|
isCaveCalculated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isCave;
|
||||||
|
}
|
||||||
|
|
||||||
public ResourcePack getResourcePack() {
|
public ResourcePack getResourcePack() {
|
||||||
return resourcePack;
|
return resourcePack;
|
||||||
}
|
}
|
@ -22,9 +22,22 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.mca;
|
package de.bluecolored.bluemap.core.world.mca;
|
||||||
|
|
||||||
public class MCAMath {
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.data.BlockStateDeserializer;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.data.KeyDeserializer;
|
||||||
|
import de.bluecolored.bluenbt.BlueNBT;
|
||||||
|
|
||||||
|
public class MCAUtil {
|
||||||
|
|
||||||
|
public static final BlueNBT BLUENBT = new BlueNBT();
|
||||||
|
static {
|
||||||
|
BLUENBT.register(TypeToken.get(BlockState.class), new BlockStateDeserializer());
|
||||||
|
BLUENBT.register(TypeToken.get(Key.class), new KeyDeserializer());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Having a long array where each long contains as many values as fit in it without overflowing, returning the "valueIndex"-th value when each value has "bitsPerValue" bits.
|
* Having a long array where each long contains as many values as fit in it without overflowing, returning the "valueIndex"-th value when each value has "bitsPerValue" bits.
|
||||||
@ -34,6 +47,7 @@ public static long getValueFromLongArray(long[] data, int valueIndex, int bitsPe
|
|||||||
int longIndex = valueIndex / valuesPerLong;
|
int longIndex = valueIndex / valuesPerLong;
|
||||||
int bitIndex = (valueIndex % valuesPerLong) * bitsPerValue;
|
int bitIndex = (valueIndex % valuesPerLong) * bitsPerValue;
|
||||||
|
|
||||||
|
if (longIndex >= data.length) return 0;
|
||||||
long value = data[longIndex] >>> bitIndex;
|
long value = data[longIndex] >>> bitIndex;
|
||||||
|
|
||||||
return value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerValue);
|
return value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerValue);
|
||||||
@ -42,16 +56,18 @@ public static long getValueFromLongArray(long[] data, int valueIndex, int bitsPe
|
|||||||
/**
|
/**
|
||||||
* Treating the long array "data" as a continuous stream of bits, returning the "valueIndex"-th value when each value has "bitsPerValue" bits.
|
* Treating the long array "data" as a continuous stream of bits, returning the "valueIndex"-th value when each value has "bitsPerValue" bits.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("ShiftOutOfRange")
|
||||||
public static long getValueFromLongStream(long[] data, int valueIndex, int bitsPerValue) {
|
public static long getValueFromLongStream(long[] data, int valueIndex, int bitsPerValue) {
|
||||||
int bitIndex = valueIndex * bitsPerValue;
|
int bitIndex = valueIndex * bitsPerValue;
|
||||||
int firstLong = bitIndex >> 6; // index / 64
|
int firstLong = bitIndex >> 6; // index / 64
|
||||||
int bitoffset = bitIndex & 0x3F; // Math.floorMod(index, 64)
|
int bitOffset = bitIndex & 0x3F; // Math.floorMod(index, 64)
|
||||||
|
|
||||||
long value = data[firstLong] >>> bitoffset;
|
if (firstLong >= data.length) return 0;
|
||||||
|
long value = data[firstLong] >>> bitOffset;
|
||||||
|
|
||||||
if (bitoffset > 0 && firstLong + 1 < data.length) {
|
if (bitOffset > 0 && firstLong + 1 < data.length) {
|
||||||
long value2 = data[firstLong + 1];
|
long value2 = data[firstLong + 1];
|
||||||
value2 = value2 << -bitoffset;
|
value2 = value2 << -bitOffset;
|
||||||
value = value | value2;
|
value = value | value2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,12 +79,12 @@ public static long getValueFromLongStream(long[] data, int valueIndex, int bitsP
|
|||||||
* The value is treated as an unsigned byte.
|
* The value is treated as an unsigned byte.
|
||||||
*/
|
*/
|
||||||
public static int getByteHalf(int value, boolean largeHalf) {
|
public static int getByteHalf(int value, boolean largeHalf) {
|
||||||
value = value & 0xFF;
|
if (largeHalf) return value >> 4 & 0xF;
|
||||||
if (largeHalf) {
|
return value & 0xF;
|
||||||
value = value >> 4;
|
|
||||||
}
|
}
|
||||||
value = value & 0xF;
|
|
||||||
return value;
|
public static int ceilLog2(int n) {
|
||||||
|
return Integer.SIZE - Integer.numberOfLeadingZeros(n - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,275 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca;
|
||||||
|
|
||||||
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
|
import com.flowpowered.math.vector.Vector3i;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
|
import de.bluecolored.bluemap.core.BlueMap;
|
||||||
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
import de.bluecolored.bluemap.core.resources.datapack.DataPack;
|
||||||
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||||
|
import de.bluecolored.bluemap.core.world.Chunk;
|
||||||
|
import de.bluecolored.bluemap.core.world.DimensionType;
|
||||||
|
import de.bluecolored.bluemap.core.world.Region;
|
||||||
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.chunk.ChunkLoader;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.data.LevelData;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.region.RegionType;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
@DebugDump
|
||||||
|
public class MCAWorld implements World {
|
||||||
|
|
||||||
|
private static final Grid CHUNK_GRID = new Grid(16);
|
||||||
|
private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID);
|
||||||
|
|
||||||
|
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final Path worldFolder;
|
||||||
|
private final Key dimension;
|
||||||
|
private final LevelData levelData;
|
||||||
|
private final DataPack dataPack;
|
||||||
|
|
||||||
|
private final DimensionType dimensionType;
|
||||||
|
private final Vector3i spawnPoint;
|
||||||
|
private final Path dimensionFolder;
|
||||||
|
private final Path regionFolder;
|
||||||
|
|
||||||
|
private final ChunkLoader chunkLoader = new ChunkLoader();
|
||||||
|
private final LoadingCache<Vector2i, Region> regionCache = Caffeine.newBuilder()
|
||||||
|
.executor(BlueMap.THREAD_POOL)
|
||||||
|
.maximumSize(64)
|
||||||
|
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||||
|
.build(this::loadRegion);
|
||||||
|
private final LoadingCache<Vector2i, Chunk> chunkCache = Caffeine.newBuilder()
|
||||||
|
.executor(BlueMap.THREAD_POOL)
|
||||||
|
.maximumSize(10240) // 10 regions worth of chunks
|
||||||
|
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||||
|
.build(this::loadChunk);
|
||||||
|
|
||||||
|
private MCAWorld(Path worldFolder, Key dimension, LevelData levelData, DataPack dataPack) {
|
||||||
|
this.id = id(worldFolder, dimension);
|
||||||
|
this.worldFolder = worldFolder;
|
||||||
|
this.dimension = dimension;
|
||||||
|
this.levelData = levelData;
|
||||||
|
this.dataPack = dataPack;
|
||||||
|
|
||||||
|
LevelData.Dimension dimensionData = levelData.getData().getWorldGenSettings().getDimensions().get(dimension.getFormatted());
|
||||||
|
if (dimensionData == null) {
|
||||||
|
if (DataPack.DIMENSION_OVERWORLD.equals(dimension)) dimensionData = new LevelData.Dimension(DataPack.DIMENSION_TYPE_OVERWORLD.getFormatted());
|
||||||
|
else if (DataPack.DIMENSION_THE_NETHER.equals(dimension)) dimensionData = new LevelData.Dimension(DataPack.DIMENSION_TYPE_THE_NETHER.getFormatted());
|
||||||
|
else if (DataPack.DIMENSION_THE_END.equals(dimension)) dimensionData = new LevelData.Dimension(DataPack.DIMENSION_TYPE_THE_END.getFormatted());
|
||||||
|
else {
|
||||||
|
Logger.global.logWarning("The level-data does not contain any dimension with the id '" + dimension +
|
||||||
|
"', using fallback.");
|
||||||
|
dimensionData = new LevelData.Dimension();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DimensionType dimensionType = dataPack.getDimensionType(new Key(dimensionData.getType()));
|
||||||
|
if (dimensionType == null) {
|
||||||
|
Logger.global.logWarning("The data-pack for world '" + worldFolder +
|
||||||
|
"' does not contain any dimension-type with the id '" + dimensionData.getType() + "', using fallback.");
|
||||||
|
dimensionType = DimensionType.OVERWORLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dimensionType = dimensionType;
|
||||||
|
this.spawnPoint = new Vector3i(
|
||||||
|
levelData.getData().getSpawnX(),
|
||||||
|
levelData.getData().getSpawnY(),
|
||||||
|
levelData.getData().getSpawnZ()
|
||||||
|
);
|
||||||
|
this.dimensionFolder = resolveDimensionFolder(worldFolder, dimension);
|
||||||
|
this.regionFolder = dimensionFolder.resolve("region");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return levelData.getData().getLevelName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Grid getChunkGrid() {
|
||||||
|
return CHUNK_GRID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Grid getRegionGrid() {
|
||||||
|
return REGION_GRID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Chunk getChunkAtBlock(int x, int z) {
|
||||||
|
return getChunk(x >> 4, z >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
return getChunk(VECTOR_2_I_CACHE.get(x, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Chunk getChunk(Vector2i pos) {
|
||||||
|
return chunkCache.get(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Region getRegion(int x, int z) {
|
||||||
|
return getRegion(VECTOR_2_I_CACHE.get(x, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region getRegion(Vector2i pos) {
|
||||||
|
return regionCache.get(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Vector2i> listRegions() {
|
||||||
|
File[] regionFiles = getRegionFolder().toFile().listFiles();
|
||||||
|
if (regionFiles == null) return Collections.emptyList();
|
||||||
|
|
||||||
|
List<Vector2i> regions = new ArrayList<>(regionFiles.length);
|
||||||
|
|
||||||
|
for (File file : regionFiles) {
|
||||||
|
if (RegionType.forFileName(file.getName()) == null) continue;
|
||||||
|
if (file.length() <= 0) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String[] filenameParts = file.getName().split("\\.");
|
||||||
|
int rX = Integer.parseInt(filenameParts[1]);
|
||||||
|
int rZ = Integer.parseInt(filenameParts[2]);
|
||||||
|
|
||||||
|
regions.add(new Vector2i(rX, rZ));
|
||||||
|
} catch (NumberFormatException ignore) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preloadRegionChunks(int x, int z) {
|
||||||
|
try {
|
||||||
|
getRegion(x, z).iterateAllChunks((cx, cz, chunk) -> {
|
||||||
|
Vector2i chunkPos = VECTOR_2_I_CACHE.get(cx, cz);
|
||||||
|
chunkCache.put(chunkPos, chunk);
|
||||||
|
});
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.global.logDebug("Unexpected exception trying to load preload region (x:" + x + ", z:" + z + "):" + ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateChunkCache() {
|
||||||
|
chunkCache.invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateChunkCache(int x, int z) {
|
||||||
|
chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanUpChunkCache() {
|
||||||
|
chunkCache.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region loadRegion(Vector2i regionPos) {
|
||||||
|
return loadRegion(regionPos.getX(), regionPos.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region loadRegion(int x, int z) {
|
||||||
|
return RegionType.loadRegion(this, getRegionFolder(), x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Chunk loadChunk(Vector2i chunkPos) {
|
||||||
|
return loadChunk(chunkPos.getX(), chunkPos.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Chunk loadChunk(int x, int z) {
|
||||||
|
final int tries = 3;
|
||||||
|
final int tryInterval = 1000;
|
||||||
|
|
||||||
|
Exception loadException = null;
|
||||||
|
for (int i = 0; i < tries; i++) {
|
||||||
|
try {
|
||||||
|
return getRegion(x >> 5, z >> 5)
|
||||||
|
.loadChunk(x, z);
|
||||||
|
} catch (IOException | RuntimeException e) {
|
||||||
|
if (loadException != null) e.addSuppressed(loadException);
|
||||||
|
loadException = e;
|
||||||
|
|
||||||
|
if (i + 1 < tries) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(tryInterval);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "):" + loadException);
|
||||||
|
return Chunk.EMPTY_CHUNK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MCAWorld load(Path worldFolder, Key dimension) throws IOException, InterruptedException {
|
||||||
|
// load level.dat
|
||||||
|
Path levelFile = worldFolder.resolve("level.dat");
|
||||||
|
InputStream levelFileIn = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(levelFile)));
|
||||||
|
LevelData levelData = MCAUtil.BLUENBT.read(levelFileIn, LevelData.class);
|
||||||
|
|
||||||
|
// load datapacks
|
||||||
|
DataPack dataPack = new DataPack();
|
||||||
|
Path dataPackFolder = worldFolder.resolve("datapacks");
|
||||||
|
if (Files.exists(dataPackFolder)) {
|
||||||
|
List<Path> roots;
|
||||||
|
try (var stream = Files.list(dataPackFolder)) {
|
||||||
|
roots = stream
|
||||||
|
.sorted(Comparator.reverseOrder())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
for (Path root : roots) {
|
||||||
|
dataPack.load(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataPack.bake();
|
||||||
|
|
||||||
|
// create world
|
||||||
|
return new MCAWorld(worldFolder, dimension, levelData, dataPack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String id(Path worldFolder, Key dimension) {
|
||||||
|
worldFolder = worldFolder.toAbsolutePath().normalize();
|
||||||
|
|
||||||
|
Path workingDir = Path.of("").toAbsolutePath().normalize();
|
||||||
|
if (worldFolder.startsWith(workingDir))
|
||||||
|
worldFolder = workingDir.relativize(worldFolder);
|
||||||
|
|
||||||
|
return "MCA#" + worldFolder + "#" + dimension.getFormatted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path resolveDimensionFolder(Path worldFolder, Key dimension) {
|
||||||
|
if (DataPack.DIMENSION_OVERWORLD.equals(dimension)) return worldFolder;
|
||||||
|
if (DataPack.DIMENSION_THE_NETHER.equals(dimension)) return worldFolder.resolve("DIM-1");
|
||||||
|
if (DataPack.DIMENSION_THE_END.equals(dimension)) return worldFolder.resolve("DIM1");
|
||||||
|
return worldFolder.resolve("dimensions").resolve(dimension.getNamespace()).resolve(dimension.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca;
|
||||||
|
|
||||||
|
public class PackedIntArrayAccess {
|
||||||
|
|
||||||
|
// magic constants for fast division
|
||||||
|
private static final int[] DIVISION_MAGIC = new int[]{
|
||||||
|
// <editor-fold defaultstate="collapsed" desc="Division-Magic Constants">
|
||||||
|
-1, -1, 0,
|
||||||
|
Integer.MIN_VALUE, 0, 0,
|
||||||
|
1431655765, 1431655765, 0,
|
||||||
|
Integer.MIN_VALUE, 0, 1,
|
||||||
|
858993459, 858993459, 0,
|
||||||
|
715827882, 715827882, 0,
|
||||||
|
613566756, 613566756, 0,
|
||||||
|
Integer.MIN_VALUE, 0, 2,
|
||||||
|
477218588, 477218588, 0,
|
||||||
|
429496729, 429496729, 0,
|
||||||
|
390451572, 390451572, 0,
|
||||||
|
357913941, 357913941, 0,
|
||||||
|
330382099, 330382099, 0,
|
||||||
|
306783378, 306783378, 0,
|
||||||
|
286331153, 286331153, 0,
|
||||||
|
Integer.MIN_VALUE, 0, 3,
|
||||||
|
252645135, 252645135, 0,
|
||||||
|
238609294, 238609294, 0,
|
||||||
|
226050910, 226050910, 0,
|
||||||
|
214748364, 214748364, 0,
|
||||||
|
204522252, 204522252, 0,
|
||||||
|
195225786, 195225786, 0,
|
||||||
|
186737708, 186737708, 0,
|
||||||
|
178956970, 178956970, 0,
|
||||||
|
171798691, 171798691, 0,
|
||||||
|
165191049, 165191049, 0,
|
||||||
|
159072862, 159072862, 0,
|
||||||
|
153391689, 153391689, 0,
|
||||||
|
148102320, 148102320, 0,
|
||||||
|
143165576, 143165576, 0,
|
||||||
|
138547332, 138547332, 0,
|
||||||
|
Integer.MIN_VALUE, 0, 4,
|
||||||
|
130150524, 130150524, 0,
|
||||||
|
126322567, 126322567, 0,
|
||||||
|
122713351, 122713351, 0,
|
||||||
|
119304647, 119304647, 0,
|
||||||
|
116080197, 116080197, 0,
|
||||||
|
113025455, 113025455, 0,
|
||||||
|
110127366, 110127366, 0,
|
||||||
|
107374182, 107374182, 0,
|
||||||
|
104755299, 104755299, 0,
|
||||||
|
102261126, 102261126, 0,
|
||||||
|
99882960, 99882960, 0,
|
||||||
|
97612893, 97612893, 0,
|
||||||
|
95443717, 95443717, 0,
|
||||||
|
93368854, 93368854, 0,
|
||||||
|
91382282, 91382282, 0,
|
||||||
|
89478485, 89478485, 0,
|
||||||
|
87652393, 87652393, 0,
|
||||||
|
85899345, 85899345, 0,
|
||||||
|
84215045, 84215045, 0,
|
||||||
|
82595524, 82595524, 0,
|
||||||
|
81037118, 81037118, 0,
|
||||||
|
79536431, 79536431, 0,
|
||||||
|
78090314, 78090314, 0,
|
||||||
|
76695844, 76695844, 0,
|
||||||
|
75350303, 75350303, 0,
|
||||||
|
74051160, 74051160, 0,
|
||||||
|
72796055, 72796055, 0,
|
||||||
|
71582788, 71582788, 0,
|
||||||
|
70409299, 70409299, 0,
|
||||||
|
69273666, 69273666, 0,
|
||||||
|
68174084, 68174084, 0,
|
||||||
|
Integer.MIN_VALUE, 0, 5
|
||||||
|
// </editor-fold>
|
||||||
|
};
|
||||||
|
|
||||||
|
private final int bitsPerElement;
|
||||||
|
private final long[] data;
|
||||||
|
|
||||||
|
private final int elementsPerLong, indexShift;
|
||||||
|
private final long maxValue, indexScale, indexOffset;
|
||||||
|
|
||||||
|
public PackedIntArrayAccess(long[] data, int elementCount) {
|
||||||
|
this(Math.max(data.length * Long.SIZE / elementCount, 1), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PackedIntArrayAccess(int bitsPerElement, long[] data) {
|
||||||
|
this.bitsPerElement = bitsPerElement;
|
||||||
|
this.data = data;
|
||||||
|
|
||||||
|
this.maxValue = (1L << this.bitsPerElement) - 1L;
|
||||||
|
this.elementsPerLong = 64 / this.bitsPerElement;
|
||||||
|
|
||||||
|
int i = 3 * (this.elementsPerLong - 1);
|
||||||
|
this.indexScale = Integer.toUnsignedLong(DIVISION_MAGIC[i]);
|
||||||
|
this.indexOffset = Integer.toUnsignedLong(DIVISION_MAGIC[i + 1]);
|
||||||
|
this.indexShift = DIVISION_MAGIC[i + 2] + 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(int i) {
|
||||||
|
int storageIndex = this.storageIndex(i);
|
||||||
|
if (storageIndex >= this.data.length) return 0;
|
||||||
|
long l = this.data[storageIndex];
|
||||||
|
int offset = (i - storageIndex * this.elementsPerLong) * this.bitsPerElement;
|
||||||
|
return (int)(l >> offset & this.maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int storageIndex(int i) {
|
||||||
|
// this is the same as doing: floor(i / elementsPerLong)
|
||||||
|
return (int) ((long) i * this.indexScale + this.indexOffset >> this.indexShift);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCapacity() {
|
||||||
|
return data.length * elementsPerLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCorrectSize(int expectedSize) {
|
||||||
|
int capacity = getCapacity();
|
||||||
|
return expectedSize <= capacity && expectedSize + elementsPerLong > capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.storage.Compression;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
public class ChunkLoader {
|
||||||
|
|
||||||
|
// sorted list of chunk-versions, loaders at the start of the list are preferred over loaders at the end
|
||||||
|
private static final List<ChunkVersionLoader<?>> CHUNK_VERSION_LOADERS = List.of(
|
||||||
|
new ChunkVersionLoader<>(Chunk_1_18.Data.class, Chunk_1_18::new, 2844),
|
||||||
|
new ChunkVersionLoader<>(Chunk_1_16.Data.class, Chunk_1_16::new, 2500),
|
||||||
|
new ChunkVersionLoader<>(Chunk_1_15.Data.class, Chunk_1_15::new, 2200),
|
||||||
|
new ChunkVersionLoader<>(Chunk_1_13.Data.class, Chunk_1_13::new, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
private ChunkVersionLoader<?> lastUsedLoader = CHUNK_VERSION_LOADERS.get(0);
|
||||||
|
|
||||||
|
public MCAChunk load(MCARegion region, byte[] data, int offset, int length, Compression compression) throws IOException {
|
||||||
|
InputStream in = new ByteArrayInputStream(data, offset, length);
|
||||||
|
in.mark(-1);
|
||||||
|
|
||||||
|
// try last used version
|
||||||
|
ChunkVersionLoader<?> usedLoader = lastUsedLoader;
|
||||||
|
MCAChunk chunk;
|
||||||
|
try (InputStream decompressedIn = new BufferedInputStream(compression.decompress(in))) {
|
||||||
|
chunk = usedLoader.load(region, decompressedIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check version and reload chunk if the wrong loader has been used and a better one has been found
|
||||||
|
ChunkVersionLoader<?> actualLoader = findBestLoaderForVersion(chunk.getDataVersion());
|
||||||
|
if (actualLoader != null && usedLoader != actualLoader) {
|
||||||
|
in.reset(); // reset read position
|
||||||
|
try (InputStream decompressedIn = new BufferedInputStream(compression.decompress(in))) {
|
||||||
|
chunk = actualLoader.load(region, decompressedIn);
|
||||||
|
}
|
||||||
|
lastUsedLoader = actualLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable ChunkVersionLoader<?> findBestLoaderForVersion(int version) {
|
||||||
|
for (ChunkVersionLoader<?> loader : CHUNK_VERSION_LOADERS) {
|
||||||
|
if (loader.mightSupport(version)) return loader;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
private static class ChunkVersionLoader<D extends MCAChunk.Data> {
|
||||||
|
|
||||||
|
private final Class<D> dataType;
|
||||||
|
private final BiFunction<MCARegion, D, MCAChunk> constructor;
|
||||||
|
private final int dataVersion;
|
||||||
|
|
||||||
|
public MCAChunk load(MCARegion region, InputStream in) throws IOException {
|
||||||
|
D data = MCAUtil.BLUENBT.read(in, dataType);
|
||||||
|
return mightSupport(data.getDataVersion()) ? constructor.apply(region, data) : new MCAChunk(region, data) {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean mightSupport(int dataVersion) {
|
||||||
|
return dataVersion >= this.dataVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,274 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import de.bluecolored.bluemap.core.world.Biome;
|
||||||
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
|
import de.bluecolored.bluemap.core.world.DimensionType;
|
||||||
|
import de.bluecolored.bluemap.core.world.LightData;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
|
||||||
|
import de.bluecolored.bluenbt.NBTName;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class Chunk_1_13 extends MCAChunk {
|
||||||
|
|
||||||
|
private static final Level EMPTY_LEVEL = new Level();
|
||||||
|
private static final HeightmapsData EMPTY_HEIGHTMAPS_DATA = new HeightmapsData();
|
||||||
|
|
||||||
|
private static final Key STATUS_EMPTY = new Key("minecraft", "empty");
|
||||||
|
private static final Key STATUS_FULL = new Key("minecraft", "full");
|
||||||
|
private static final Key STATUS_FULLCHUNK = new Key("minecraft", "fullchunk");
|
||||||
|
private static final Key STATUS_POSTPROCESSED = new Key("minecraft", "postprocessed");
|
||||||
|
|
||||||
|
private final boolean generated;
|
||||||
|
private final boolean hasLightData;
|
||||||
|
private final long inhabitedTime;
|
||||||
|
|
||||||
|
private final int skyLight;
|
||||||
|
|
||||||
|
private final boolean hasWorldSurfaceHeights;
|
||||||
|
private final long[] worldSurfaceHeights;
|
||||||
|
private final boolean hasOceanFloorHeights;
|
||||||
|
private final long[] oceanFloorHeights;
|
||||||
|
|
||||||
|
private final Section[] sections;
|
||||||
|
private final int sectionMin, sectionMax;
|
||||||
|
|
||||||
|
final int[] biomes;
|
||||||
|
|
||||||
|
public Chunk_1_13(MCARegion region, Data data) {
|
||||||
|
super(region, data);
|
||||||
|
|
||||||
|
Level level = data.level;
|
||||||
|
|
||||||
|
this.generated = !STATUS_EMPTY.equals(level.status);
|
||||||
|
this.hasLightData =
|
||||||
|
STATUS_FULL.equals(level.status) ||
|
||||||
|
STATUS_FULLCHUNK.equals(level.status) ||
|
||||||
|
STATUS_POSTPROCESSED.equals(level.status);
|
||||||
|
this.inhabitedTime = level.inhabitedTime;
|
||||||
|
|
||||||
|
DimensionType dimensionType = getRegion().getWorld().getDimensionType();
|
||||||
|
this.skyLight = dimensionType.hasSkylight() ? 16 : 0;
|
||||||
|
|
||||||
|
this.worldSurfaceHeights = level.heightmaps.worldSurface;
|
||||||
|
this.oceanFloorHeights = level.heightmaps.oceanFloor;
|
||||||
|
|
||||||
|
this.hasWorldSurfaceHeights = this.worldSurfaceHeights.length >= 36;
|
||||||
|
this.hasOceanFloorHeights = this.oceanFloorHeights.length >= 36;
|
||||||
|
|
||||||
|
this.biomes = level.biomes;
|
||||||
|
|
||||||
|
SectionData[] sectionsData = level.sections;
|
||||||
|
if (sectionsData != null && sectionsData.length > 0) {
|
||||||
|
int min = Integer.MAX_VALUE;
|
||||||
|
int max = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
// find section min/max y
|
||||||
|
for (SectionData sectionData : sectionsData) {
|
||||||
|
int y = sectionData.getY();
|
||||||
|
if (min > y) min = y;
|
||||||
|
if (max < y) max = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load sections into ordered array
|
||||||
|
this.sections = new Section[1 + max - min];
|
||||||
|
for (SectionData sectionData : sectionsData) {
|
||||||
|
Section section = new Section(sectionData);
|
||||||
|
int y = section.getSectionY();
|
||||||
|
|
||||||
|
if (min > y) min = y;
|
||||||
|
if (max < y) max = y;
|
||||||
|
|
||||||
|
this.sections[section.sectionY - min] = section;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sectionMin = min;
|
||||||
|
this.sectionMax = max;
|
||||||
|
} else {
|
||||||
|
this.sections = new Section[0];
|
||||||
|
this.sectionMin = 0;
|
||||||
|
this.sectionMax = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGenerated() {
|
||||||
|
return generated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasLightData() {
|
||||||
|
return hasLightData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getInhabitedTime() {
|
||||||
|
return inhabitedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getBlockState(int x, int y, int z) {
|
||||||
|
Section section = getSection(y >> 4);
|
||||||
|
if (section == null) return BlockState.AIR;
|
||||||
|
|
||||||
|
return section.getBlockState(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBiome(int x, int y, int z) {
|
||||||
|
if (this.biomes.length < 256) return Biome.DEFAULT.getFormatted();
|
||||||
|
|
||||||
|
int biomeIntIndex = (z & 0xF) << 4 | x & 0xF;
|
||||||
|
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||||
|
if (!hasLightData) return target.set(skyLight, 0);
|
||||||
|
|
||||||
|
int sectionY = y >> 4;
|
||||||
|
Section section = getSection(sectionY);
|
||||||
|
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(skyLight, 0);
|
||||||
|
|
||||||
|
return section.getLightData(x, y, z, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinY(int x, int z) {
|
||||||
|
return sectionMin * 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxY(int x, int z) {
|
||||||
|
return sectionMax * 16 + 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasWorldSurfaceHeights() {
|
||||||
|
return hasWorldSurfaceHeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorldSurfaceY(int x, int z) {
|
||||||
|
return (int) MCAUtil.getValueFromLongStream(
|
||||||
|
worldSurfaceHeights,
|
||||||
|
(z & 0xF) << 4 | x & 0xF,
|
||||||
|
9
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasOceanFloorHeights() {
|
||||||
|
return hasOceanFloorHeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOceanFloorY(int x, int z) {
|
||||||
|
return (int) MCAUtil.getValueFromLongStream(
|
||||||
|
oceanFloorHeights,
|
||||||
|
(z & 0xF) << 4 | x & 0xF,
|
||||||
|
9
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable Section getSection(int y) {
|
||||||
|
y -= sectionMin;
|
||||||
|
if (y < 0 || y >= this.sections.length) return null;
|
||||||
|
return this.sections[y];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Section {
|
||||||
|
|
||||||
|
private final int sectionY;
|
||||||
|
private final BlockState[] blockPalette;
|
||||||
|
private final long[] blocks;
|
||||||
|
private final byte[] blockLight;
|
||||||
|
private final byte[] skyLight;
|
||||||
|
|
||||||
|
private final int bitsPerBlock;
|
||||||
|
|
||||||
|
public Section(SectionData sectionData) {
|
||||||
|
this.sectionY = sectionData.y;
|
||||||
|
|
||||||
|
this.blockPalette = sectionData.palette;
|
||||||
|
this.blocks = sectionData.blockStates;
|
||||||
|
|
||||||
|
this.blockLight = sectionData.getBlockLight();
|
||||||
|
this.skyLight = sectionData.getSkyLight();
|
||||||
|
|
||||||
|
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState getBlockState(int x, int y, int z) {
|
||||||
|
if (blockPalette.length == 1) return blockPalette[0];
|
||||||
|
if (blockPalette.length == 0) return BlockState.AIR;
|
||||||
|
|
||||||
|
int id = (int) MCAUtil.getValueFromLongStream(
|
||||||
|
blocks,
|
||||||
|
(y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF,
|
||||||
|
bitsPerBlock
|
||||||
|
);
|
||||||
|
if (id >= blockPalette.length) {
|
||||||
|
Logger.global.noFloodWarning("palette-warning", "Got block-palette id " + id + " but palette has size of " + blockPalette.length + "! (Future occasions of this error will not be logged)");
|
||||||
|
return BlockState.MISSING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockPalette[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||||
|
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||||
|
|
||||||
|
int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF;
|
||||||
|
int blockHalfByteIndex = blockByteIndex >> 1;
|
||||||
|
boolean largeHalf = (blockByteIndex & 0x1) != 0;
|
||||||
|
|
||||||
|
return target.set(
|
||||||
|
this.skyLight.length > blockHalfByteIndex ? MCAUtil.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||||
|
this.blockLight.length > blockHalfByteIndex ? MCAUtil.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSectionY() {
|
||||||
|
return sectionY;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
public static class Data extends MCAChunk.Data {
|
||||||
|
private Level level = EMPTY_LEVEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
public static class Level {
|
||||||
|
private Key status = STATUS_EMPTY;
|
||||||
|
private long inhabitedTime = 0;
|
||||||
|
private HeightmapsData heightmaps = EMPTY_HEIGHTMAPS_DATA;
|
||||||
|
private SectionData @Nullable [] sections = null;
|
||||||
|
private int[] biomes = EMPTY_INT_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
protected static class HeightmapsData {
|
||||||
|
@NBTName("WORLD_SURFACE") private long[] worldSurface = EMPTY_LONG_ARRAY;
|
||||||
|
@NBTName("OCEAN_FLOOR") private long[] oceanFloor = EMPTY_LONG_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
protected static class SectionData {
|
||||||
|
private int y = 0;
|
||||||
|
private byte[] blockLight = EMPTY_BYTE_ARRAY;
|
||||||
|
private byte[] skyLight = EMPTY_BYTE_ARRAY;
|
||||||
|
private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY;
|
||||||
|
private long[] blockStates = EMPTY_LONG_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.world.Biome;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
|
||||||
|
|
||||||
|
public class Chunk_1_15 extends Chunk_1_13 {
|
||||||
|
|
||||||
|
public Chunk_1_15(MCARegion region, Data data) {
|
||||||
|
super(region, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBiome(int x, int y, int z) {
|
||||||
|
if (this.biomes.length < 16) return Biome.DEFAULT.getFormatted();
|
||||||
|
|
||||||
|
int biomeIntIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2;
|
||||||
|
|
||||||
|
// shift y up/down if not in range
|
||||||
|
if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16;
|
||||||
|
if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16;
|
||||||
|
|
||||||
|
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,262 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import de.bluecolored.bluemap.core.world.Biome;
|
||||||
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
|
import de.bluecolored.bluemap.core.world.DimensionType;
|
||||||
|
import de.bluecolored.bluemap.core.world.LightData;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.PackedIntArrayAccess;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
|
||||||
|
import de.bluecolored.bluenbt.NBTName;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class Chunk_1_16 extends MCAChunk {
|
||||||
|
|
||||||
|
private static final Level EMPTY_LEVEL = new Level();
|
||||||
|
private static final HeightmapsData EMPTY_HEIGHTMAPS_DATA = new HeightmapsData();
|
||||||
|
|
||||||
|
private static final Key STATUS_EMPTY = new Key("minecraft", "empty");
|
||||||
|
private static final Key STATUS_FULL = new Key("minecraft", "full");
|
||||||
|
|
||||||
|
private final boolean generated;
|
||||||
|
private final boolean hasLightData;
|
||||||
|
private final long inhabitedTime;
|
||||||
|
|
||||||
|
private final int skyLight;
|
||||||
|
|
||||||
|
private final boolean hasWorldSurfaceHeights;
|
||||||
|
private final PackedIntArrayAccess worldSurfaceHeights;
|
||||||
|
private final boolean hasOceanFloorHeights;
|
||||||
|
private final PackedIntArrayAccess oceanFloorHeights;
|
||||||
|
|
||||||
|
private final Section[] sections;
|
||||||
|
private final int sectionMin, sectionMax;
|
||||||
|
|
||||||
|
private final int[] biomes;
|
||||||
|
|
||||||
|
public Chunk_1_16(MCARegion region, Data data) {
|
||||||
|
super(region, data);
|
||||||
|
|
||||||
|
Level level = data.level;
|
||||||
|
|
||||||
|
this.generated = !STATUS_EMPTY.equals(level.status);
|
||||||
|
this.hasLightData = STATUS_FULL.equals(level.status);
|
||||||
|
this.inhabitedTime = level.inhabitedTime;
|
||||||
|
|
||||||
|
DimensionType dimensionType = getRegion().getWorld().getDimensionType();
|
||||||
|
this.skyLight = dimensionType.hasSkylight() ? 16 : 0;
|
||||||
|
|
||||||
|
int worldHeight = dimensionType.getHeight();
|
||||||
|
int bitsPerHeightmapElement = MCAUtil.ceilLog2(worldHeight + 1);
|
||||||
|
|
||||||
|
this.worldSurfaceHeights = new PackedIntArrayAccess(bitsPerHeightmapElement, level.heightmaps.worldSurface);
|
||||||
|
this.oceanFloorHeights = new PackedIntArrayAccess(bitsPerHeightmapElement, level.heightmaps.oceanFloor);
|
||||||
|
|
||||||
|
this.hasWorldSurfaceHeights = this.worldSurfaceHeights.isCorrectSize(VALUES_PER_HEIGHTMAP);
|
||||||
|
this.hasOceanFloorHeights = this.oceanFloorHeights.isCorrectSize(VALUES_PER_HEIGHTMAP);
|
||||||
|
|
||||||
|
this.biomes = level.biomes;
|
||||||
|
|
||||||
|
SectionData[] sectionsData = level.sections;
|
||||||
|
if (sectionsData != null && sectionsData.length > 0) {
|
||||||
|
int min = Integer.MAX_VALUE;
|
||||||
|
int max = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
// find section min/max y
|
||||||
|
for (SectionData sectionData : sectionsData) {
|
||||||
|
int y = sectionData.getY();
|
||||||
|
if (min > y) min = y;
|
||||||
|
if (max < y) max = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load sections into ordered array
|
||||||
|
this.sections = new Section[1 + max - min];
|
||||||
|
for (SectionData sectionData : sectionsData) {
|
||||||
|
Section section = new Section(sectionData);
|
||||||
|
int y = section.getSectionY();
|
||||||
|
|
||||||
|
if (min > y) min = y;
|
||||||
|
if (max < y) max = y;
|
||||||
|
|
||||||
|
this.sections[section.sectionY - min] = section;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sectionMin = min;
|
||||||
|
this.sectionMax = max;
|
||||||
|
} else {
|
||||||
|
this.sections = new Section[0];
|
||||||
|
this.sectionMin = 0;
|
||||||
|
this.sectionMax = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGenerated() {
|
||||||
|
return generated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasLightData() {
|
||||||
|
return hasLightData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getInhabitedTime() {
|
||||||
|
return inhabitedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getBlockState(int x, int y, int z) {
|
||||||
|
Section section = getSection(y >> 4);
|
||||||
|
if (section == null) return BlockState.AIR;
|
||||||
|
|
||||||
|
return section.getBlockState(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBiome(int x, int y, int z) {
|
||||||
|
if (this.biomes.length < 16) return Biome.DEFAULT.getFormatted();
|
||||||
|
|
||||||
|
int biomeIntIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2;
|
||||||
|
|
||||||
|
// shift y up/down if not in range
|
||||||
|
if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16;
|
||||||
|
if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16;
|
||||||
|
|
||||||
|
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||||
|
if (!hasLightData) return target.set(skyLight, 0);
|
||||||
|
|
||||||
|
int sectionY = y >> 4;
|
||||||
|
Section section = getSection(sectionY);
|
||||||
|
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(skyLight, 0);
|
||||||
|
|
||||||
|
return section.getLightData(x, y, z, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinY(int x, int z) {
|
||||||
|
return sectionMin * 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxY(int x, int z) {
|
||||||
|
return sectionMax * 16 + 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasWorldSurfaceHeights() {
|
||||||
|
return hasWorldSurfaceHeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorldSurfaceY(int x, int z) {
|
||||||
|
return worldSurfaceHeights.get((z & 0xF) << 4 | x & 0xF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasOceanFloorHeights() {
|
||||||
|
return hasOceanFloorHeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOceanFloorY(int x, int z) {
|
||||||
|
return oceanFloorHeights.get((z & 0xF) << 4 | x & 0xF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable Section getSection(int y) {
|
||||||
|
y -= sectionMin;
|
||||||
|
if (y < 0 || y >= this.sections.length) return null;
|
||||||
|
return this.sections[y];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Section {
|
||||||
|
|
||||||
|
private final int sectionY;
|
||||||
|
private final BlockState[] blockPalette;
|
||||||
|
private final PackedIntArrayAccess blocks;
|
||||||
|
private final byte[] blockLight;
|
||||||
|
private final byte[] skyLight;
|
||||||
|
|
||||||
|
public Section(SectionData sectionData) {
|
||||||
|
this.sectionY = sectionData.y;
|
||||||
|
|
||||||
|
this.blockPalette = sectionData.palette;
|
||||||
|
this.blocks = new PackedIntArrayAccess(sectionData.blockStates, BLOCKS_PER_SECTION);
|
||||||
|
|
||||||
|
this.blockLight = sectionData.getBlockLight();
|
||||||
|
this.skyLight = sectionData.getSkyLight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState getBlockState(int x, int y, int z) {
|
||||||
|
if (blockPalette.length == 1) return blockPalette[0];
|
||||||
|
if (blockPalette.length == 0) return BlockState.AIR;
|
||||||
|
|
||||||
|
int id = blocks.get((y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF);
|
||||||
|
if (id >= blockPalette.length) {
|
||||||
|
Logger.global.noFloodWarning("palette-warning", "Got block-palette id " + id + " but palette has size of " + blockPalette.length + "! (Future occasions of this error will not be logged)");
|
||||||
|
return BlockState.MISSING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockPalette[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||||
|
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||||
|
|
||||||
|
int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF;
|
||||||
|
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||||
|
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||||
|
|
||||||
|
return target.set(
|
||||||
|
this.skyLight.length > blockHalfByteIndex ? MCAUtil.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||||
|
this.blockLight.length > blockHalfByteIndex ? MCAUtil.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSectionY() {
|
||||||
|
return sectionY;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
public static class Data extends MCAChunk.Data {
|
||||||
|
private Level level = EMPTY_LEVEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
public static class Level {
|
||||||
|
private Key status = STATUS_EMPTY;
|
||||||
|
private long inhabitedTime = 0;
|
||||||
|
private HeightmapsData heightmaps = EMPTY_HEIGHTMAPS_DATA;
|
||||||
|
private SectionData @Nullable [] sections = null;
|
||||||
|
private int[] biomes = EMPTY_INT_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
protected static class HeightmapsData {
|
||||||
|
@NBTName("WORLD_SURFACE") private long[] worldSurface = EMPTY_LONG_ARRAY;
|
||||||
|
@NBTName("OCEAN_FLOOR") private long[] oceanFloor = EMPTY_LONG_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
protected static class SectionData {
|
||||||
|
private int y = 0;
|
||||||
|
private byte[] blockLight = EMPTY_BYTE_ARRAY;
|
||||||
|
private byte[] skyLight = EMPTY_BYTE_ARRAY;
|
||||||
|
private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY;
|
||||||
|
private long[] blockStates = EMPTY_LONG_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,279 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import de.bluecolored.bluemap.core.world.Biome;
|
||||||
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
|
import de.bluecolored.bluemap.core.world.DimensionType;
|
||||||
|
import de.bluecolored.bluemap.core.world.LightData;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.PackedIntArrayAccess;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
|
||||||
|
import de.bluecolored.bluenbt.NBTName;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class Chunk_1_18 extends MCAChunk {
|
||||||
|
|
||||||
|
private static final BlockStatesData EMPTY_BLOCKSTATESDATA = new BlockStatesData();
|
||||||
|
private static final BiomesData EMPTY_BIOMESDATA = new BiomesData();
|
||||||
|
private static final HeightmapsData EMPTY_HEIGHTMAPS_DATA = new HeightmapsData();
|
||||||
|
|
||||||
|
private static final Key STATUS_EMPTY = new Key("minecraft", "empty");
|
||||||
|
private static final Key STATUS_FULL = new Key("minecraft", "full");
|
||||||
|
|
||||||
|
private final boolean generated;
|
||||||
|
private final boolean hasLightData;
|
||||||
|
private final long inhabitedTime;
|
||||||
|
|
||||||
|
private final int skyLight;
|
||||||
|
private final int worldMinY;
|
||||||
|
|
||||||
|
private final boolean hasWorldSurfaceHeights;
|
||||||
|
private final PackedIntArrayAccess worldSurfaceHeights;
|
||||||
|
private final boolean hasOceanFloorHeights;
|
||||||
|
private final PackedIntArrayAccess oceanFloorHeights;
|
||||||
|
|
||||||
|
private final Section[] sections;
|
||||||
|
private final int sectionMin, sectionMax;
|
||||||
|
|
||||||
|
public Chunk_1_18(MCARegion region, Data data) {
|
||||||
|
super(region, data);
|
||||||
|
|
||||||
|
this.generated = !STATUS_EMPTY.equals(data.status);
|
||||||
|
this.hasLightData = STATUS_FULL.equals(data.status);
|
||||||
|
this.inhabitedTime = data.inhabitedTime;
|
||||||
|
|
||||||
|
DimensionType dimensionType = getRegion().getWorld().getDimensionType();
|
||||||
|
this.worldMinY = dimensionType.getMinY();
|
||||||
|
this.skyLight = dimensionType.hasSkylight() ? 16 : 0;
|
||||||
|
|
||||||
|
int worldHeight = dimensionType.getHeight();
|
||||||
|
int bitsPerHeightmapElement = MCAUtil.ceilLog2(worldHeight + 1);
|
||||||
|
|
||||||
|
this.worldSurfaceHeights = new PackedIntArrayAccess(bitsPerHeightmapElement, data.heightmaps.worldSurface);
|
||||||
|
this.oceanFloorHeights = new PackedIntArrayAccess(bitsPerHeightmapElement, data.heightmaps.oceanFloor);
|
||||||
|
|
||||||
|
this.hasWorldSurfaceHeights = this.worldSurfaceHeights.isCorrectSize(VALUES_PER_HEIGHTMAP);
|
||||||
|
this.hasOceanFloorHeights = this.oceanFloorHeights.isCorrectSize(VALUES_PER_HEIGHTMAP);
|
||||||
|
|
||||||
|
SectionData[] sectionsData = data.sections;
|
||||||
|
if (sectionsData != null && sectionsData.length > 0) {
|
||||||
|
int min = Integer.MAX_VALUE;
|
||||||
|
int max = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
// find section min/max y
|
||||||
|
for (SectionData sectionData : sectionsData) {
|
||||||
|
int y = sectionData.getY();
|
||||||
|
if (min > y) min = y;
|
||||||
|
if (max < y) max = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load sections into ordered array
|
||||||
|
this.sections = new Section[1 + max - min];
|
||||||
|
for (SectionData sectionData : sectionsData) {
|
||||||
|
Section section = new Section(sectionData);
|
||||||
|
int y = section.getSectionY();
|
||||||
|
|
||||||
|
if (min > y) min = y;
|
||||||
|
if (max < y) max = y;
|
||||||
|
|
||||||
|
this.sections[section.sectionY - min] = section;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sectionMin = min;
|
||||||
|
this.sectionMax = max;
|
||||||
|
} else {
|
||||||
|
this.sections = new Section[0];
|
||||||
|
this.sectionMin = 0;
|
||||||
|
this.sectionMax = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGenerated() {
|
||||||
|
return generated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasLightData() {
|
||||||
|
return hasLightData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getInhabitedTime() {
|
||||||
|
return inhabitedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getBlockState(int x, int y, int z) {
|
||||||
|
Section section = getSection(y >> 4);
|
||||||
|
if (section == null) return BlockState.AIR;
|
||||||
|
|
||||||
|
return section.getBlockState(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBiome(int x, int y, int z) {
|
||||||
|
Section section = getSection(y >> 4);
|
||||||
|
if (section == null) return Biome.DEFAULT.getFormatted();
|
||||||
|
|
||||||
|
return section.getBiome(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||||
|
if (!hasLightData) return target.set(skyLight, 0);
|
||||||
|
|
||||||
|
int sectionY = y >> 4;
|
||||||
|
Section section = getSection(sectionY);
|
||||||
|
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(skyLight, 0);
|
||||||
|
|
||||||
|
return section.getLightData(x, y, z, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinY(int x, int z) {
|
||||||
|
return sectionMin * 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxY(int x, int z) {
|
||||||
|
return sectionMax * 16 + 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasWorldSurfaceHeights() {
|
||||||
|
return hasWorldSurfaceHeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorldSurfaceY(int x, int z) {
|
||||||
|
return worldSurfaceHeights.get((z & 0xF) << 4 | x & 0xF) + worldMinY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasOceanFloorHeights() {
|
||||||
|
return hasOceanFloorHeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOceanFloorY(int x, int z) {
|
||||||
|
return oceanFloorHeights.get((z & 0xF) << 4 | x & 0xF) + worldMinY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable Section getSection(int y) {
|
||||||
|
y -= sectionMin;
|
||||||
|
if (y < 0 || y >= this.sections.length) return null;
|
||||||
|
return this.sections[y];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Section {
|
||||||
|
|
||||||
|
private final int sectionY;
|
||||||
|
private final BlockState[] blockPalette;
|
||||||
|
private final String[] biomePalette;
|
||||||
|
private final PackedIntArrayAccess blocks;
|
||||||
|
private final PackedIntArrayAccess biomes;
|
||||||
|
private final byte[] blockLight;
|
||||||
|
private final byte[] skyLight;
|
||||||
|
|
||||||
|
public Section(SectionData sectionData) {
|
||||||
|
this.sectionY = sectionData.y;
|
||||||
|
|
||||||
|
this.blockPalette = sectionData.blockStates.palette;
|
||||||
|
this.biomePalette = sectionData.biomes.palette;
|
||||||
|
|
||||||
|
this.blocks = new PackedIntArrayAccess(sectionData.blockStates.data, BLOCKS_PER_SECTION);
|
||||||
|
this.biomes = new PackedIntArrayAccess(sectionData.biomes.data, BIOMES_PER_SECTION);
|
||||||
|
|
||||||
|
this.blockLight = sectionData.blockLight;
|
||||||
|
this.skyLight = sectionData.skyLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState getBlockState(int x, int y, int z) {
|
||||||
|
if (blockPalette.length == 1) return blockPalette[0];
|
||||||
|
if (blockPalette.length == 0) return BlockState.AIR;
|
||||||
|
|
||||||
|
int id = blocks.get((y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF);
|
||||||
|
if (id >= blockPalette.length) {
|
||||||
|
Logger.global.noFloodWarning("palette-warning", "Got block-palette id " + id + " but palette has size of " + blockPalette.length + "! (Future occasions of this error will not be logged)");
|
||||||
|
return BlockState.MISSING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockPalette[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBiome(int x, int y, int z) {
|
||||||
|
if (biomePalette.length == 1) return biomePalette[0];
|
||||||
|
if (biomePalette.length == 0) return Biome.DEFAULT.getValue();
|
||||||
|
|
||||||
|
int id = biomes.get((y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2);
|
||||||
|
if (id >= biomePalette.length) {
|
||||||
|
Logger.global.noFloodWarning("biome-palette-warning", "Got biome-palette id " + id + " but palette has size of " + biomePalette.length + "! (Future occasions of this error will not be logged)");
|
||||||
|
return Biome.DEFAULT.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return biomePalette[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||||
|
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||||
|
|
||||||
|
int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF;
|
||||||
|
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||||
|
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||||
|
|
||||||
|
return target.set(
|
||||||
|
this.skyLight.length > blockHalfByteIndex ? MCAUtil.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||||
|
this.blockLight.length > blockHalfByteIndex ? MCAUtil.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSectionY() {
|
||||||
|
return sectionY;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
public static class Data extends MCAChunk.Data {
|
||||||
|
private Key status = STATUS_EMPTY;
|
||||||
|
private long inhabitedTime = 0;
|
||||||
|
private HeightmapsData heightmaps = EMPTY_HEIGHTMAPS_DATA;
|
||||||
|
private SectionData @Nullable [] sections = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
protected static class HeightmapsData {
|
||||||
|
@NBTName("WORLD_SURFACE") private long[] worldSurface = EMPTY_LONG_ARRAY;
|
||||||
|
@NBTName("OCEAN_FLOOR") private long[] oceanFloor = EMPTY_LONG_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
protected static class SectionData {
|
||||||
|
private int y = 0;
|
||||||
|
private byte[] blockLight = EMPTY_BYTE_ARRAY;
|
||||||
|
private byte[] skyLight = EMPTY_BYTE_ARRAY;
|
||||||
|
@NBTName("block_states") private BlockStatesData blockStates = EMPTY_BLOCKSTATESDATA;
|
||||||
|
private BiomesData biomes = EMPTY_BIOMESDATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
protected static class BlockStatesData {
|
||||||
|
private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY;
|
||||||
|
private long[] data = EMPTY_LONG_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
protected static class BiomesData {
|
||||||
|
private String[] palette = EMPTY_STRING_ARRAY;
|
||||||
|
private long[] data = EMPTY_LONG_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,7 +22,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.mca;
|
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -0,0 +1,37 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
|
import de.bluecolored.bluemap.core.world.Chunk;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public abstract class MCAChunk implements Chunk {
|
||||||
|
|
||||||
|
protected static final int BLOCKS_PER_SECTION = 16 * 16 * 16;
|
||||||
|
protected static final int BIOMES_PER_SECTION = 4 * 4 * 4;
|
||||||
|
protected static final int VALUES_PER_HEIGHTMAP = 16 * 16;
|
||||||
|
|
||||||
|
protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||||
|
protected static final int[] EMPTY_INT_ARRAY = new int[0];
|
||||||
|
protected static final long[] EMPTY_LONG_ARRAY = new long[0];
|
||||||
|
protected static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||||
|
protected static final BlockState[] EMPTY_BLOCKSTATE_ARRAY = new BlockState[0];
|
||||||
|
|
||||||
|
private final MCARegion region;
|
||||||
|
private final int dataVersion;
|
||||||
|
|
||||||
|
public MCAChunk(MCARegion region, Data chunkData) {
|
||||||
|
this.region = region;
|
||||||
|
this.dataVersion = chunkData.getDataVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
@Getter
|
||||||
|
public static class Data {
|
||||||
|
private int dataVersion = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.data;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.world.BlockState;
|
||||||
|
import de.bluecolored.bluenbt.NBTReader;
|
||||||
|
import de.bluecolored.bluenbt.TypeDeserializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BlockStateDeserializer implements TypeDeserializer<BlockState> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState read(NBTReader reader) throws IOException {
|
||||||
|
reader.beginCompound();
|
||||||
|
|
||||||
|
String id = null;
|
||||||
|
Map<String, String> properties = null;
|
||||||
|
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
switch (reader.name()) {
|
||||||
|
case "Name" : id = reader.nextString(); break;
|
||||||
|
case "Properties" :
|
||||||
|
properties = new LinkedHashMap<>();
|
||||||
|
reader.beginCompound();
|
||||||
|
while (reader.hasNext())
|
||||||
|
properties.put(reader.name(), reader.nextString());
|
||||||
|
reader.endCompound();
|
||||||
|
break;
|
||||||
|
default : reader.skip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.endCompound();
|
||||||
|
|
||||||
|
if (id == null) throw new IOException("Invalid BlockState, Name is missing!");
|
||||||
|
return properties == null ? new BlockState(id) : new BlockState(id, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.data;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
|
import de.bluecolored.bluenbt.NBTReader;
|
||||||
|
import de.bluecolored.bluenbt.TypeDeserializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class KeyDeserializer implements TypeDeserializer<Key> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key read(NBTReader reader) throws IOException {
|
||||||
|
return new Key(reader.nextString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package de.bluecolored.bluemap.core.world.mca.data;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
@DebugDump
|
||||||
|
public class LevelData {
|
||||||
|
|
||||||
|
private Data data = new Data();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@DebugDump
|
||||||
|
public static class Data {
|
||||||
|
private String levelName = "world";
|
||||||
|
private int spawnX = 0, spawnY = 0, spawnZ = 0;
|
||||||
|
private WGSettings worldGenSettings = new WGSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@DebugDump
|
||||||
|
public static class WGSettings {
|
||||||
|
private Map<String, Dimension> dimensions = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@DebugDump
|
||||||
|
public static class Dimension {
|
||||||
|
private String type = "minecraft:overworld";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* 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.world.mca.region;
|
||||||
|
|
||||||
|
import com.flowpowered.math.vector.Vector2i;
|
||||||
|
import de.bluecolored.bluemap.core.storage.Compression;
|
||||||
|
import de.bluecolored.bluemap.core.world.Chunk;
|
||||||
|
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||||
|
import de.bluecolored.bluemap.core.world.Region;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||||
|
import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class MCARegion implements Region {
|
||||||
|
|
||||||
|
public static final String FILE_SUFFIX = ".mca";
|
||||||
|
|
||||||
|
private final MCAWorld world;
|
||||||
|
private final Path regionFile;
|
||||||
|
private final Vector2i regionPos;
|
||||||
|
|
||||||
|
public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
|
||||||
|
this.world = world;
|
||||||
|
this.regionFile = regionFile;
|
||||||
|
|
||||||
|
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
|
||||||
|
int rX = Integer.parseInt(filenameParts[1]);
|
||||||
|
int rZ = Integer.parseInt(filenameParts[2]);
|
||||||
|
|
||||||
|
this.regionPos = new Vector2i(rX, rZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MCARegion(MCAWorld world, Vector2i regionPos) throws IllegalArgumentException {
|
||||||
|
this.world = world;
|
||||||
|
this.regionPos = regionPos;
|
||||||
|
this.regionFile = world.getRegionFolder().resolve(getRegionFileName(regionPos.getX(), regionPos.getY()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
|
||||||
|
if (Files.notExists(regionFile)) return Chunk.EMPTY_CHUNK;
|
||||||
|
|
||||||
|
long fileLength = Files.size(regionFile);
|
||||||
|
if (fileLength == 0) return Chunk.EMPTY_CHUNK;
|
||||||
|
|
||||||
|
try (FileChannel channel = FileChannel.open(regionFile, StandardOpenOption.READ)) {
|
||||||
|
int xzChunk = (chunkZ & 0b11111) << 5 | (chunkX & 0b11111);
|
||||||
|
|
||||||
|
byte[] header = new byte[4];
|
||||||
|
channel.position(xzChunk * 4);
|
||||||
|
readFully(channel, header, 0, 4);
|
||||||
|
|
||||||
|
int offset = header[0] << 16;
|
||||||
|
offset |= (header[1] & 0xFF) << 8;
|
||||||
|
offset |= header[2] & 0xFF;
|
||||||
|
offset *= 4096;
|
||||||
|
int size = header[3] * 4096;
|
||||||
|
|
||||||
|
if (size == 0) return Chunk.EMPTY_CHUNK;
|
||||||
|
|
||||||
|
byte[] chunkDataBuffer = new byte[size];
|
||||||
|
|
||||||
|
channel.position(offset);
|
||||||
|
readFully(channel, chunkDataBuffer, 0, size);
|
||||||
|
|
||||||
|
return loadChunk(chunkDataBuffer, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void iterateAllChunks(ChunkConsumer consumer) throws IOException {
|
||||||
|
if (Files.notExists(regionFile)) return;
|
||||||
|
|
||||||
|
long fileLength = Files.size(regionFile);
|
||||||
|
if (fileLength == 0) return;
|
||||||
|
|
||||||
|
int chunkStartX = regionPos.getX() * 32;
|
||||||
|
int chunkStartZ = regionPos.getY() * 32;
|
||||||
|
|
||||||
|
try (FileChannel channel = FileChannel.open(regionFile, StandardOpenOption.READ)) {
|
||||||
|
byte[] header = new byte[1024 * 8];
|
||||||
|
byte[] chunkDataBuffer = null;
|
||||||
|
|
||||||
|
// read the header
|
||||||
|
readFully(channel, header, 0, header.length);
|
||||||
|
|
||||||
|
// iterate over all chunks
|
||||||
|
for (int x = 0; x < 32; x++) {
|
||||||
|
for (int z = 0; z < 32; z++) {
|
||||||
|
int xzChunk = z * 32 + x;
|
||||||
|
|
||||||
|
int size = header[xzChunk * 4 + 3] * 4096;
|
||||||
|
if (size == 0) continue;
|
||||||
|
|
||||||
|
int chunkX = chunkStartX + x;
|
||||||
|
int chunkZ = chunkStartZ + z;
|
||||||
|
|
||||||
|
int i = xzChunk * 4 + 4096;
|
||||||
|
int timestamp = header[i++] << 24;
|
||||||
|
timestamp |= (header[i++] & 0xFF) << 16;
|
||||||
|
timestamp |= (header[i++] & 0xFF) << 8;
|
||||||
|
timestamp |= header[i] & 0xFF;
|
||||||
|
|
||||||
|
// load chunk only if consumers filter returns true
|
||||||
|
if (consumer.filter(chunkX, chunkZ, timestamp)) {
|
||||||
|
i = xzChunk * 4;
|
||||||
|
int offset = header[i++] << 16;
|
||||||
|
offset |= (header[i++] & 0xFF) << 8;
|
||||||
|
offset |= header[i] & 0xFF;
|
||||||
|
offset *= 4096;
|
||||||
|
|
||||||
|
if (chunkDataBuffer == null || chunkDataBuffer.length < size)
|
||||||
|
chunkDataBuffer = new byte[size];
|
||||||
|
|
||||||
|
channel.position(offset);
|
||||||
|
readFully(channel, chunkDataBuffer, 0, size);
|
||||||
|
|
||||||
|
MCAChunk chunk = loadChunk(chunkDataBuffer, size);
|
||||||
|
consumer.accept(chunkX, chunkZ, chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MCAChunk loadChunk(byte[] data, int size) throws IOException {
|
||||||
|
int compressionTypeId = data[4];
|
||||||
|
Compression compression;
|
||||||
|
switch (compressionTypeId) {
|
||||||
|
case 0 :
|
||||||
|
case 3 : compression = Compression.NONE; break;
|
||||||
|
case 1 : compression = Compression.GZIP; break;
|
||||||
|
case 2 : compression = Compression.DEFLATE; break;
|
||||||
|
case 4 : compression = Compression.LZ4; break;
|
||||||
|
default: throw new IOException("Unknown chunk compression-id: " + compressionTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return world.getChunkLoader().load(this, data, 5, size - 5, compression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRegionFileName(int regionX, int regionZ) {
|
||||||
|
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private static void readFully(ReadableByteChannel src, byte[] dst, int off, int len) throws IOException {
|
||||||
|
readFully(src, ByteBuffer.wrap(dst), off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readFully(ReadableByteChannel src, ByteBuffer bb, int off, int len) throws IOException {
|
||||||
|
int limit = off + len;
|
||||||
|
if (limit > bb.capacity()) throw new IllegalArgumentException("buffer too small");
|
||||||
|
|
||||||
|
bb.limit(limit);
|
||||||
|
bb.position(off);
|
||||||
|
|
||||||
|
do {
|
||||||
|
int read = src.read(bb);
|
||||||
|
if (read < 0) throw new EOFException();
|
||||||
|
} while (bb.remaining() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user