BlueMap/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java

342 lines
12 KiB
Java

/*
* 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.common;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.config.ConfigManager;
import de.bluecolored.bluemap.core.config.CoreConfig;
import de.bluecolored.bluemap.core.config.MapConfig;
import de.bluecolored.bluemap.core.config.RenderConfig;
import de.bluecolored.bluemap.core.config.WebServerConfig;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.render.RenderSettings;
import de.bluecolored.bluemap.core.render.TileRenderer;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.web.WebFilesManager;
import de.bluecolored.bluemap.core.web.WebSettings;
import de.bluecolored.bluemap.core.world.SlicedWorld;
import de.bluecolored.bluemap.core.world.World;
/**
* This is the attempt to generalize as many actions as possible to have CLI and Plugins run on the same general setup-code.
*/
public class BlueMapService {
private MinecraftVersion minecraftVersion;
private File configFolder;
private ThrowingFunction<File, UUID, IOException> worldUUIDProvider;
private ThrowingFunction<UUID, String, IOException> worldNameProvider;
private ConfigManager configManager;
private boolean resourceConfigLoaded = false;
private CoreConfig coreConfig;
private RenderConfig renderConfig;
private WebServerConfig webServerConfig;
private ResourcePack resourcePack;
private Map<UUID, World> worlds;
private Map<String, MapType> maps;
public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) {
this.minecraftVersion = minecraftVersion;
this.configFolder = configFolder;
Map<File, UUID> uuids = new HashMap<>();
this.worldUUIDProvider = file -> {
UUID uuid = uuids.get(file);
if (uuid == null) {
uuid = UUID.randomUUID();
uuids.put(file, uuid);
}
return uuid;
};
this.worldNameProvider = uuid -> null;
configManager = new ConfigManager();
}
public BlueMapService(MinecraftVersion minecraftVersion, ServerInterface serverInterface) {
this.minecraftVersion = minecraftVersion;
this.configFolder = serverInterface.getConfigFolder();
this.worldUUIDProvider = serverInterface::getUUIDForWorld;
this.worldNameProvider = serverInterface::getWorldName;
this.configManager = new ConfigManager();
}
public synchronized void createOrUpdateWebApp(boolean force) throws IOException {
WebFilesManager webFilesManager = new WebFilesManager(getRenderConfig().getWebRoot());
if (force || webFilesManager.needsUpdate()) {
webFilesManager.updateFiles();
}
}
public synchronized WebSettings updateWebAppSettings() throws IOException {
WebSettings webSettings = new WebSettings(new File(getRenderConfig().getWebRoot(), "data" + File.separator + "settings.json"));
webSettings.set(getRenderConfig().isUseCookies(), "useCookies");
webSettings.setAllMapsEnabled(false);
for (MapType map : getMaps().values()) {
webSettings.setMapEnabled(true, map.getId());
webSettings.setFrom(map.getTileRenderer(), map.getId());
webSettings.setFrom(map.getWorld(), map.getId());
}
int ordinal = 0;
for (MapConfig map : getRenderConfig().getMapConfigs()) {
if (!getMaps().containsKey(map.getId())) continue; //don't add not loaded maps
webSettings.setOrdinal(ordinal++, map.getId());
webSettings.setFrom(map, map.getId());
}
webSettings.save();
return webSettings;
}
public synchronized Map<UUID, World> getWorlds() throws IOException {
if (worlds == null) loadWorldsAndMaps();
return worlds;
}
public synchronized Map<String, MapType> getMaps() throws IOException {
if (maps == null) loadWorldsAndMaps();
return maps;
}
private synchronized void loadWorldsAndMaps() throws IOException {
maps = new HashMap<>();
worlds = new HashMap<>();
for (MapConfig mapConfig : getRenderConfig().getMapConfigs()) {
String id = mapConfig.getId();
String name = mapConfig.getName();
File worldFolder = new File(mapConfig.getWorldPath());
if (!worldFolder.exists() || !worldFolder.isDirectory()) {
Logger.global.logWarning("Failed to load map '" + id + "': '" + worldFolder.getCanonicalPath() + "' does not exist or is no directory!");
continue;
}
UUID worldUUID;
try {
worldUUID = worldUUIDProvider.apply(worldFolder);
} catch (IOException e) {
Logger.global.logError("Failed to load map '" + id + "': Failed to get UUID for the world!", e);
continue;
}
World world = worlds.get(worldUUID);
if (world == null) {
try {
world = MCAWorld.load(worldFolder.toPath(), worldUUID, minecraftVersion, getConfigManager().getBlockIdConfig(), getConfigManager().getBlockPropertiesConfig(), getConfigManager().getBiomeConfig(), worldNameProvider.apply(worldUUID), true);
worlds.put(worldUUID, world);
} catch (MissingResourcesException e) {
throw e; // rethrow this to stop loading and display resource-missing message
} catch (IOException e) {
Logger.global.logError("Failed to load map '" + id + "': Failed to read level.dat", e);
continue;
}
}
//slice world if configured
if (!mapConfig.getMin().equals(RenderSettings.DEFAULT_MIN) || !mapConfig.getMax().equals(RenderSettings.DEFAULT_MAX)) {
if (mapConfig.isRenderEdges()) {
world = new SlicedWorld(world, mapConfig.getMin(), mapConfig.getMax());
} else {
world = new SlicedWorld(
world,
mapConfig.getMin().min(mapConfig.getMin().sub(2, 2, 2)), // protect from int-overflow
mapConfig.getMax().max(mapConfig.getMax().add(2, 2, 2)) // protect from int-overflow
);
}
}
HiresModelManager hiresModelManager = new HiresModelManager(
getRenderConfig().getWebRoot().toPath().resolve("data").resolve(id).resolve("hires"),
getResourcePack(),
mapConfig,
new Vector2i(mapConfig.getHiresTileSize(), mapConfig.getHiresTileSize())
);
LowresModelManager lowresModelManager = new LowresModelManager(
getRenderConfig().getWebRoot().toPath().resolve("data").resolve(id).resolve("lowres"),
new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()),
new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile()),
mapConfig.useGzipCompression()
);
TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager);
MapType mapType = new MapType(id, name, world, tileRenderer);
maps.put(id, mapType);
}
worlds = Collections.unmodifiableMap(worlds);
maps = Collections.unmodifiableMap(maps);
}
public synchronized ResourcePack getResourcePack() throws IOException, MissingResourcesException {
if (resourcePack == null) {
File defaultResourceFile = new File(getCoreConfig().getDataFolder(), "minecraft-client-" + minecraftVersion.getVersionString() + ".jar");
File resourceExtensionsFile = new File(getCoreConfig().getDataFolder(), "resourceExtensions.zip");
File textureExportFile = new File(getRenderConfig().getWebRoot(), "data" + File.separator + "textures.json");
if (!defaultResourceFile.exists()) {
if (getCoreConfig().isDownloadAccepted()) {
//download file
try {
Logger.global.logInfo("Downloading " + minecraftVersion.getClientDownloadUrl() + " to " + defaultResourceFile + " ...");
FileUtils.copyURLToFile(new URL(minecraftVersion.getClientDownloadUrl()), defaultResourceFile, 10000, 10000);
} catch (IOException e) {
throw new IOException("Failed to download resources!", e);
}
} else {
throw new MissingResourcesException();
}
}
Logger.global.logInfo("Loading resources...");
resourceExtensionsFile.delete();
FileUtils.copyURLToFile(Plugin.class.getResource("/de/bluecolored/bluemap/" + minecraftVersion.getResourcePrefix() + "/resourceExtensions.zip"), resourceExtensionsFile, 10000, 10000);
//find more resource packs
File resourcePackFolder = new File(configFolder, "resourcepacks");
resourcePackFolder.mkdirs();
File[] resourcePacks = resourcePackFolder.listFiles();
Arrays.sort(resourcePacks); //load resource packs in alphabetical order so you can reorder them by renaming
List<File> resources = new ArrayList<>(resourcePacks.length + 1);
resources.add(defaultResourceFile);
for (File file : resourcePacks) resources.add(file);
resources.add(resourceExtensionsFile);
try {
resourcePack = new ResourcePack(minecraftVersion);
if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile);
resourcePack.load(resources);
resourcePack.saveTextureFile(textureExportFile);
} catch (ParseResourceException e) {
throw new IOException("Failed to parse resources!", e);
}
}
return resourcePack;
}
public synchronized ConfigManager getConfigManager() throws IOException {
if (!resourceConfigLoaded) {
configManager.loadResourceConfigs(configFolder, getResourcePack());
resourceConfigLoaded = true;
}
return configManager;
}
public File getCoreConfigFile() {
return new File(configFolder, "core.conf");
}
public synchronized CoreConfig getCoreConfig() throws IOException {
if (coreConfig == null) {
coreConfig = new CoreConfig(configManager.loadOrCreate(
getCoreConfigFile(),
Plugin.class.getResource("/de/bluecolored/bluemap/core.conf"),
Plugin.class.getResource("/de/bluecolored/bluemap/core-defaults.conf"),
true,
true
));
}
return coreConfig;
}
public File getRenderConfigFile() {
return new File(configFolder, "render.conf");
}
public synchronized RenderConfig getRenderConfig() throws IOException {
if (renderConfig == null) {
renderConfig = new RenderConfig(configManager.loadOrCreate(
getRenderConfigFile(),
Plugin.class.getResource("/de/bluecolored/bluemap/render.conf"),
Plugin.class.getResource("/de/bluecolored/bluemap/render-defaults.conf"),
true,
true
));
}
return renderConfig;
}
public File getWebServerConfigFile() {
return new File(configFolder, "webserver.conf");
}
public synchronized WebServerConfig getWebServerConfig() throws IOException {
if (webServerConfig == null) {
webServerConfig = new WebServerConfig(configManager.loadOrCreate(
getWebServerConfigFile(),
Plugin.class.getResource("/de/bluecolored/bluemap/webserver.conf"),
Plugin.class.getResource("/de/bluecolored/bluemap/webserver-defaults.conf"),
true,
true
));
}
return webServerConfig;
}
public File getConfigFolder() {
return configFolder;
}
}