diff --git a/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java index 802bbce8..1a093586 100644 --- a/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java +++ b/BlueMapBukkit/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java @@ -104,7 +104,7 @@ private UUID getUUIDForWorldSync (File worldFolder) throws IOException { if (worldFolder.equals(world.getWorldFolder().getCanonicalFile())) return world.getUID(); } - throw new IOException("There is no world with this folder loaded: " + worldFolder.getCanonicalPath()); + throw new IOException("There is no world with this folder loaded: " + worldFolder.getPath()); } @Override diff --git a/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf b/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf index a0c813f4..e2e574da 100644 --- a/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf +++ b/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf @@ -24,7 +24,7 @@ renderThreadCount: -2 # If this is true, BlueMap might send really basic metrics reports containg only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/ # This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :) -# An example report looks like this: {"implementation":"CLI","version":"%version%"} +# An example report looks like this: {"implementation":"bukkit","version":"%version%"} metrics: true # The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later. @@ -74,6 +74,10 @@ maps: [ # The path to the save-folder of the world to render world: "world" + + # The position on the world where the map will be centered if you open it. + # This defaults to the world-spawn if you don't set it. + #startPos: [500, -820] # If this is false, BlueMap tries to omit all blocks that are not visible from above-ground. # More specific: Block-Faces that have a sunlight/skylight value of 0 are removed. diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index 881d4a21..4f03cfc9 100644 --- a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -24,8 +24,14 @@ */ package de.bluecolored.bluemap.cli; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; @@ -36,6 +42,8 @@ import java.util.UUID; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; @@ -136,15 +144,14 @@ public void renderMaps() throws IOException { webSettings.setAllEnabled(false); for (MapType map : maps.values()) { webSettings.setEnabled(true, map.getId()); - webSettings.setName(map.getName(), map.getId()); webSettings.setFrom(map.getTileRenderer(), map.getId()); + webSettings.setFrom(map.getWorld(), map.getId()); } int ordinal = 0; for (MapConfig map : config.getMapConfigs()) { if (!maps.containsKey(map.getId())) continue; //don't add not loaded maps webSettings.setOrdinal(ordinal++, map.getId()); - webSettings.setHiresViewDistance(map.getHiresViewDistance(), map.getId()); - webSettings.setLowresViewDistance(map.getLowresViewDistance(), map.getId()); + webSettings.setFrom(map, map.getId()); } webSettings.save(); @@ -153,86 +160,118 @@ public void renderMaps() throws IOException { resourcePack.saveTextureFile(textureExportFile); RenderManager renderManager = new RenderManager(config.getRenderThreadCount()); + File rmstate = new File(configFolder, "rmstate"); + + if (rmstate.exists()) { + try ( + InputStream in = new GZIPInputStream(new FileInputStream(rmstate)); + DataInputStream din = new DataInputStream(in); + ){ + renderManager.readState(din, maps.values()); + Logger.global.logInfo("Found unfinished render, continuing ... (If you want to start a new render, delete the this file: " + rmstate.getCanonicalPath()); + } catch (IOException ex) { + Logger.global.logError("Failed to read saved render-state! Remove the file " + rmstate.getCanonicalPath() + " to start a new render.", ex); + } + } else { + for (MapType map : maps.values()) { + Logger.global.logInfo("Creating render-task for map '" + map.getId() + "' ..."); + Logger.global.logInfo("Collecting tiles ..."); + + Collection chunks; + if (!forceRender) { + long lastRender = webSettings.getLong(map.getId(), "last-render"); + chunks = map.getWorld().getChunkList(lastRender); + } else { + chunks = map.getWorld().getChunkList(); + } + + HiresModelManager hiresModelManager = map.getTileRenderer().getHiresModelManager(); + Collection tiles = hiresModelManager.getTilesForChunks(chunks); + Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)"); + if (!forceRender && chunks.size() == 0) { + Logger.global.logInfo("(This is normal if nothing has changed in the world since the last render. Use -f on the command-line to force a render of all chunks)"); + } + + if (tiles.isEmpty()) { + continue; + } + + RenderTask task = new RenderTask(map.getName(), map); + task.addTiles(tiles); + task.optimizeQueue(); + + renderManager.addRenderTask(task); + } + } + + Logger.global.logInfo("Starting render ..."); renderManager.start(); - for (MapType map : maps.values()) { - Logger.global.logInfo("Rendering map '" + map.getId() + "' ..."); - Logger.global.logInfo("Collecting tiles to render..."); - - Collection chunks; - if (!forceRender) { - long lastRender = webSettings.getLong(map.getId(), "last-render"); - chunks = map.getWorld().getChunkList(lastRender); - } else { - chunks = map.getWorld().getChunkList(); - } - - HiresModelManager hiresModelManager = map.getTileRenderer().getHiresModelManager(); - Collection tiles = hiresModelManager.getTilesForChunks(chunks); - Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)"); - if (!forceRender && chunks.size() == 0) { - Logger.global.logInfo("(This is normal if nothing has changed in the world since the last render. Use -f on the command-line to force a render of all chunks)"); - } - - if (tiles.isEmpty()) { - continue; - } + long startTime = System.currentTimeMillis(); - Logger.global.logInfo("Starting Render..."); - long starttime = System.currentTimeMillis(); - - RenderTask task = new RenderTask("Map-Render: " + map.getName(), map); - task.addTiles(tiles); - task.optimizeQueue(); - - renderManager.addRenderTask(task); - - long lastLogUpdate = System.currentTimeMillis(); - long lastSave = lastLogUpdate; - - while(!task.isFinished()) { - try { - Thread.sleep(200); - } catch (InterruptedException e) {} - - long now = System.currentTimeMillis(); - - if (lastLogUpdate < now - 10000) { // print update all 10 seconds - lastLogUpdate = now; - long time = task.getActiveTime(); - - String durationString = DurationFormatUtils.formatDurationWords(time, true, true); - int tileCount = task.getRemainingTileCount() + task.getRenderedTileCount(); - double pct = (double)task.getRenderedTileCount() / (double) tileCount; - - long ert = (long)((time / pct) * (1d - pct)); - String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true); - - double tps = task.getRenderedTileCount() / (time / 1000.0); - - Logger.global.logInfo("Rendered " + task.getRenderedTileCount() + " of " + tileCount + " tiles in " + durationString + " | " + GenericMath.round(tps, 3) + " tiles/s"); - Logger.global.logInfo(GenericMath.round(pct * 100, 3) + "% | Estimated remaining time: " + ertDurationString); - } - - if (lastSave < now - 5 * 60000) { // save every 5 minutes - lastSave = now; - map.getTileRenderer().save(); - } - } - - map.getTileRenderer().save(); - + long lastLogUpdate = startTime; + long lastSave = startTime; + + while(renderManager.getRenderTaskCount() != 0) { try { - webSettings.set(starttime, map.getId(), "last-render"); - webSettings.save(); - } catch (IOException e) { - Logger.global.logError("Failed to update web-settings!", e); + Thread.sleep(200); + } catch (InterruptedException e) {} + + + long now = System.currentTimeMillis(); + + if (lastLogUpdate < now - 10000) { // print update all 10 seconds + RenderTask currentTask = renderManager.getCurrentRenderTask(); + lastLogUpdate = now; + long time = currentTask.getActiveTime(); + + String durationString = DurationFormatUtils.formatDurationWords(time, true, true); + int tileCount = currentTask.getRemainingTileCount() + currentTask.getRenderedTileCount(); + double pct = (double)currentTask.getRenderedTileCount() / (double) tileCount; + + long ert = (long)((time / pct) * (1d - pct)); + String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true); + + double tps = currentTask.getRenderedTileCount() / (time / 1000.0); + + Logger.global.logInfo("Rendering map '" + currentTask.getName() + "':"); + Logger.global.logInfo("Rendered " + currentTask.getRenderedTileCount() + " of " + tileCount + " tiles in " + durationString + " | " + GenericMath.round(tps, 3) + " tiles/s"); + Logger.global.logInfo(GenericMath.round(pct * 100, 3) + "% | Estimated remaining time: " + ertDurationString); + } + + if (lastSave < now - 1 * 60000) { // save every minute + RenderTask currentTask = renderManager.getCurrentRenderTask(); + + lastSave = now; + currentTask.getMapType().getTileRenderer().save(); + + try ( + OutputStream os = new GZIPOutputStream(new FileOutputStream(rmstate)); + DataOutputStream dos = new DataOutputStream(os); + ){ + renderManager.writeState(dos); + } catch (IOException ex) { + Logger.global.logError("Failed to save render-state!", ex); + } } } renderManager.stop(); - Logger.global.logInfo("Waiting for all threads to quit..."); + //render finished, so remove render state file + rmstate.delete(); + + for (MapType map : maps.values()) { + webSettings.set(startTime, map.getId(), "last-render"); + } + + try { + webSettings.save(); + } catch (IOException e) { + Logger.global.logError("Failed to update web-settings!", e); + } + + Logger.global.logInfo("Waiting for all threads to quit ..."); if (!ForkJoinPool.commonPool().awaitQuiescence(30, TimeUnit.SECONDS)) { Logger.global.logWarning("Some save-threads are taking very long to exit (>30s), they will be ignored."); } @@ -241,14 +280,14 @@ public void renderMaps() throws IOException { } public void startWebserver() throws IOException { - Logger.global.logInfo("Starting webserver..."); + Logger.global.logInfo("Starting webserver ..."); BlueMapWebServer webserver = new BlueMapWebServer(configManager.getMainConfig()); webserver.start(); } private boolean loadResources() throws IOException, ParseResourceException { - Logger.global.logInfo("Loading resources..."); + Logger.global.logInfo("Loading resources ..."); MainConfig config = configManager.getMainConfig(); diff --git a/BlueMapCLI/src/main/resources/bluemap-cli.conf b/BlueMapCLI/src/main/resources/bluemap-cli.conf index 8034fdf1..4e913278 100644 --- a/BlueMapCLI/src/main/resources/bluemap-cli.conf +++ b/BlueMapCLI/src/main/resources/bluemap-cli.conf @@ -24,7 +24,7 @@ renderThreadCount: 0 # If this is true, BlueMap might send really basic metrics reports containg only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/ # This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :) -# An example report looks like this: {"implementation":"CLI","version":"%version%"} +# An example report looks like this: {"implementation":"cli","version":"%version%"} metrics: true # The folder where bluemap saves data-files it needs during runtime @@ -73,6 +73,10 @@ maps: [ # The path to the save-folder of the world to render world: "world" + + # The position on the world where the map will be centered if you open it. + # This defaults to the world-spawn if you don't set it. + #startPos: [500, -820] # If this is false, BlueMap tries to omit all blocks that are not visible from above-ground. # More specific: Block-Faces that have a sunlight/skylight value of 0 are removed. diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java index b0236457..ac5f12e9 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java @@ -127,8 +127,10 @@ private void renderThread() { RenderTask task = renderTasks.peek(); if (task != null) { ticket = task.poll(); - if (task.isFinished()) renderTasks.poll(); - task.getMapType().getTileRenderer().save(); + if (task.isFinished()) { + renderTasks.poll(); + task.getMapType().getTileRenderer().save(); + } } } } @@ -155,7 +157,17 @@ public int getQueueSize() { * Returns a copy of the deque with the render tasks in order as array */ public RenderTask[] getRenderTasks(){ - return renderTasks.toArray(new RenderTask[renderTasks.size()]); + synchronized (renderTasks) { + return renderTasks.toArray(new RenderTask[renderTasks.size()]); + } + } + + public int getRenderTaskCount(){ + return renderTasks.size(); + } + + public RenderTask getCurrentRenderTask() { + return renderTasks.peek(); } public boolean isRunning() { diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java index f990f61a..9479222e 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -69,6 +69,7 @@ public class Plugin { private RenderManager renderManager; private BlueMapWebServer webServer; + private Thread periodicalSaveThread; private Thread metricsThread; private boolean loaded = false; @@ -218,6 +219,23 @@ public synchronized void load() throws IOException, ParseResourceException { Logger.global.logError("Failed to load render-manager state!", ex); } + //create periodical-save thread + periodicalSaveThread = new Thread(() -> { + try { + while (true) { + Thread.sleep(TimeUnit.MINUTES.toMillis(5)); + try { + saveRenderManagerState(); + } catch (IOException ex) { + Logger.global.logError("Failed to save render-manager state!", ex); + } + } + } catch (InterruptedException ex){ + return; + } + }); + periodicalSaveThread.start(); + //start map updater this.updateHandler = new MapUpdateHandler(); serverInterface.registerListener(updateHandler); @@ -232,15 +250,14 @@ public synchronized void load() throws IOException, ParseResourceException { webSettings.setAllEnabled(false); for (MapType map : maps.values()) { webSettings.setEnabled(true, map.getId()); - webSettings.setName(map.getName(), map.getId()); webSettings.setFrom(map.getTileRenderer(), map.getId()); + webSettings.setFrom(map.getWorld(), map.getId()); } int ordinal = 0; for (MapConfig map : config.getMapConfigs()) { if (!maps.containsKey(map.getId())) continue; //don't add not loaded maps webSettings.setOrdinal(ordinal++, map.getId()); - webSettings.setHiresViewDistance(map.getHiresViewDistance(), map.getId()); - webSettings.setLowresViewDistance(map.getLowresViewDistance(), map.getId()); + webSettings.setFrom(map, map.getId()); } webSettings.save(); @@ -278,6 +295,9 @@ public synchronized void unload() { if (metricsThread != null) metricsThread.interrupt(); metricsThread = null; + if (periodicalSaveThread != null) periodicalSaveThread.interrupt(); + periodicalSaveThread = null; + //stop services if (renderManager != null) renderManager.stop(); if (webServer != null) webServer.close(); @@ -286,14 +306,7 @@ public synchronized void unload() { if (updateHandler != null) updateHandler.flushTileBuffer(); //first write all buffered tiles to the render manager to save them too if (renderManager != null) { try { - File saveFile = config.getDataPath().resolve("rmstate").toFile(); - saveFile.getParentFile().mkdirs(); - if (saveFile.exists()) saveFile.delete(); - saveFile.createNewFile(); - - try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) { - renderManager.writeState(out); - } + saveRenderManagerState(); } catch (IOException ex) { Logger.global.logError("Failed to save render-manager state!", ex); } @@ -315,6 +328,17 @@ public synchronized void unload() { loaded = false; } + + public void saveRenderManagerState() throws IOException { + File saveFile = config.getDataPath().resolve("rmstate").toFile(); + saveFile.getParentFile().mkdirs(); + if (saveFile.exists()) saveFile.delete(); + saveFile.createNewFile(); + + try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) { + renderManager.writeState(out); + } + } public synchronized void reload() throws IOException, ParseResourceException { unload(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MainConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MainConfig.java index e7fa381e..bd00d402 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MainConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MainConfig.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.List; +import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; import com.google.common.base.Preconditions; @@ -194,6 +195,8 @@ public class MapConfig implements RenderSettings { private String name; private String world; + private Vector2i startPos; + private boolean renderCaves; private float ambientOcclusion; private float lighting; @@ -219,6 +222,8 @@ private MapConfig(ConfigurationNode node) throws IOException { this.world = node.getNode("world").getString(""); if (world.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].world is not defined"); + if (!node.getNode("startPos").isVirtual()) this.startPos = ConfigUtils.readVector2i(node.getNode("startPos")); + this.renderCaves = node.getNode("renderCaves").getBoolean(false); this.ambientOcclusion = node.getNode("ambientOcclusion").getFloat(0.25f); this.lighting = node.getNode("lighting").getFloat(0.8f); @@ -234,7 +239,7 @@ private MapConfig(ConfigurationNode node) throws IOException { this.renderEdges = node.getNode("renderEdges").getBoolean(true); - this.renderEdges = node.getNode("useCompression").getBoolean(true); + this.useGzip = node.getNode("useCompression").getBoolean(true); this.hiresTileSize = node.getNode("hires", "tileSize").getInt(32); this.hiresViewDistance = node.getNode("hires", "viewDistance").getFloat(4.5f); @@ -259,6 +264,10 @@ public String getName() { public String getWorldPath() { return world; } + + public Vector2i getStartPos() { + return startPos; + } public boolean isRenderCaves() { return renderCaves; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java index a69f3c28..bd23cc62 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java @@ -83,9 +83,8 @@ public HiresModel render(WorldTile tile, AABB region) { } catch (NoSuchResourceException e2) { e.addSuppressed(e2); blockModel = new BlockStateModel(); - } - - Logger.global.noFloodDebug(block.getBlockState().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlockState() + " (" + e.toString() + ")"); + } + //Logger.global.noFloodDebug(block.getBlockState().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlockState() + " (" + e.toString() + ")"); } blockModel.translate(new Vector3f(x, y, z).sub(modelMin.toFloat())); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java index dcc29242..c67ea7d1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java @@ -66,7 +66,7 @@ public Collection getModels(BlockState blockState Variant allMatch = null; for (Variant variant : variants) { if (variant.condition.matches(blockState)) { - if (variant.condition instanceof All) { //only use "all" conditioned if nothing else matched + if (variant.condition instanceof All) { //only use "all" condition if nothing else matched if (allMatch == null) allMatch = variant; continue; } @@ -106,16 +106,21 @@ private Variant() { } public TransformedBlockModelResource getModel(Vector3i pos) { + if (models.isEmpty()) throw new IllegalStateException("A variant must have at least one model!"); + double selection = MathUtils.hashToFloat(pos, 827364) * totalWeight; // random based on position for (Weighted w : models) { selection -= w.weight; - if (selection < 0) - return w.value; + if (selection <= 0) return w.value; } throw new RuntimeException("This line should never be reached!"); } + public void checkValid() throws ParseResourceException { + if (models.isEmpty()) throw new ParseResourceException("A variant must have at least one model!"); + } + public void updateTotalWeight() { totalWeight = 0d; for (Weighted w : models) { @@ -181,10 +186,11 @@ public BlockStateResource build(String blockstateFile) throws IOException { variant.models = loadModels(transformedModelNode, blockstateFile, null); variant.updateTotalWeight(); + variant.checkValid(); blockState.variants.add(variant); - } catch (Exception ex) { - Logger.global.logWarning("Failed to parse a variant of " + blockstateFile + ": " + ex); + } catch (Throwable t) { + Logger.global.logWarning("Failed to parse a variant of " + blockstateFile + ": " + t); } } @@ -199,10 +205,11 @@ public BlockStateResource build(String blockstateFile) throws IOException { variant.models = loadModels(partNode.getNode("apply"), blockstateFile, null); variant.updateTotalWeight(); + variant.checkValid(); blockState.multipart.add(variant); - } catch (Exception ex) { - Logger.global.logWarning("Failed to parse a multipart-part of " + blockstateFile + ": " + ex); + } catch (Throwable t) { + Logger.global.logWarning("Failed to parse a multipart-part of " + blockstateFile + ": " + t); } } @@ -364,7 +371,14 @@ private BlockStateResource buildForge(ConfigurationNode config, String blockstat } variant.updateTotalWeight(); - blockState.variants.add(variant); + + try { + variant.checkValid(); + blockState.variants.add(variant); + } catch (ParseResourceException ex) { + Logger.global.logWarning("Failed to parse a variant (forge/property) of " + blockstateFile + ": " + ex); + } + } //create default straight variant @@ -390,7 +404,14 @@ private BlockStateResource buildForge(ConfigurationNode config, String blockstat } variant.updateTotalWeight(); - blockState.variants.add(variant); + + try { + variant.checkValid(); + blockState.variants.add(variant); + } catch (ParseResourceException ex) { + Logger.global.logWarning("Failed to parse a variant (forge/straight) of " + blockstateFile + ": " + ex); + } + } return blockState; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java index 89cb8ef7..6fa593da 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java @@ -31,7 +31,9 @@ import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.config.MainConfig.MapConfig; import de.bluecolored.bluemap.core.render.TileRenderer; +import de.bluecolored.bluemap.core.world.World; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.gson.GsonConfigurationLoader; import ninja.leaping.configurate.loader.ConfigurationLoader; @@ -128,13 +130,23 @@ public void setFrom(TileRenderer tileRenderer, String mapId) { set(pointSize.getX() / 2, mapId, "lowres", "translate", "x"); set(pointSize.getY() / 2, mapId, "lowres", "translate", "z"); } - - public void setHiresViewDistance(float hiresViewDistance, String mapId) { - set(hiresViewDistance, mapId, "hires", "viewDistance"); + + public void setFrom(World world, String mapId) { + set(world.getSpawnPoint().getX(), mapId, "startPos", "x"); + set(world.getSpawnPoint().getZ(), mapId, "startPos", "z"); } - public void setLowresViewDistance(float lowresViewDistance, String mapId) { - set(lowresViewDistance, mapId, "lowres", "viewDistance"); + public void setFrom(MapConfig mapConfig, String mapId) { + Vector2i startPos = mapConfig.getStartPos(); + if (startPos != null) { + set(startPos.getX(), mapId, "startPos", "x"); + set(startPos.getY(), mapId, "startPos", "z"); + } + + set(mapConfig.getLowresViewDistance(), mapId, "lowres", "viewDistance"); + set(mapConfig.getHiresViewDistance(), mapId, "hires", "viewDistance"); + + setName(mapConfig.getName(), mapId); } public void setOrdinal(int ordinal, String mapId) { diff --git a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js index 9cbb6532..777dc504 100644 --- a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js +++ b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js @@ -81,32 +81,17 @@ export default class BlueMap { this.controls = new Controls(this.camera, this.element, this.hiresScene); this.loadSettings().then(async () => { - this.controls.setTileSize(this.settings[this.map]['hires']['tileSize']); - - this.lowresTileManager = new TileManager( - this, - this.settings[this.map]['lowres']['viewDistance'], - this.loadLowresTile, - this.lowresScene, - this.settings[this.map]['lowres']['tileSize'], - {x: 0, z: 0} - ); - - this.hiresTileManager = new TileManager( - this, - this.settings[this.map]['hires']['viewDistance'], - this.loadHiresTile, - this.hiresScene, - this.settings[this.map]['hires']['tileSize'], - {x: 0, z: 0} - ); - await this.loadHiresMaterial(); await this.loadLowresMaterial(); + this.changeMap(this.map); + this.initModules(); this.start(); - }).catch(error => this.onLoadError(error.toString())); + }).catch(error => { + this.onLoadError(error.toString()) + console.error(error); + }); } initModules() { @@ -119,12 +104,20 @@ export default class BlueMap { } changeMap(map) { - this.hiresTileManager.close(); - this.lowresTileManager.close(); + if (this.hiresTileManager !== undefined) this.hiresTileManager.close(); + if (this.lowresTileManager !== undefined) this.lowresTileManager.close(); this.map = map; + + let startPos = { + x: this.settings[this.map]["startPos"]["x"], + z: this.settings[this.map]["startPos"]["z"] + }; + this.controls.setTileSize(this.settings[this.map]['hires']['tileSize']); this.controls.resetPosition(); + this.controls.targetPosition.set(startPos.x, this.controls.targetPosition.y, startPos.z); + this.controls.position.copy(this.controls.targetPosition); this.lowresTileManager = new TileManager( this, @@ -132,7 +125,7 @@ export default class BlueMap { this.loadLowresTile, this.lowresScene, this.settings[this.map]['lowres']['tileSize'], - {x: 0, z: 0} + startPos ); this.hiresTileManager = new TileManager( @@ -141,7 +134,7 @@ export default class BlueMap { this.loadHiresTile, this.hiresScene, this.settings[this.map]['hires']['tileSize'], - {x: 0, z: 0} + startPos ); this.lowresTileManager.update(); @@ -214,14 +207,17 @@ export default class BlueMap { } this.locationHash = - '#' + this.map - + ':' + Math.floor(this.controls.targetPosition.x) - + ':' + Math.floor(this.controls.targetPosition.z) - + ':' + Math.round(this.controls.targetDirection * 100) / 100 - + ':' + Math.round(this.controls.targetDistance * 100) / 100 - + ':' + Math.ceil(this.controls.targetAngle * 100) / 100 - + ':' + Math.floor(this.controls.targetPosition.y); - history.replaceState(undefined, undefined, this.locationHash); + '#' + this.map + + ':' + Math.floor(this.controls.targetPosition.x) + + ':' + Math.floor(this.controls.targetPosition.z) + + ':' + Math.round(this.controls.targetDirection * 100) / 100 + + ':' + Math.round(this.controls.targetDistance * 100) / 100 + + ':' + Math.ceil(this.controls.targetAngle * 100) / 100 + + ':' + Math.floor(this.controls.targetPosition.y); + // only update hash when changed + if (window.location.hash !== this.locationHash) { + history.replaceState(undefined, undefined, this.locationHash); + } }; render = () => { diff --git a/BlueMapCore/src/main/webroot/js/libs/TileManager.js b/BlueMapCore/src/main/webroot/js/libs/TileManager.js index 6640a3a9..37783879 100644 --- a/BlueMapCore/src/main/webroot/js/libs/TileManager.js +++ b/BlueMapCore/src/main/webroot/js/libs/TileManager.js @@ -37,7 +37,8 @@ export default class TileManager { this.scene = scene; this.tileSize = new Vector2(tileSize.x, tileSize.z); - this.tile = new Vector2(position.x, position.z); + this.tile = new Vector2(0, 0); + this.tile.set(position.x, position.z).divide(this.tileSize).floor(); this.lastTile = this.tile.clone(); this.closed = false; diff --git a/BlueMapSponge/src/main/resources/bluemap-sponge.conf b/BlueMapSponge/src/main/resources/bluemap-sponge.conf index a12c4bf6..280b9b37 100644 --- a/BlueMapSponge/src/main/resources/bluemap-sponge.conf +++ b/BlueMapSponge/src/main/resources/bluemap-sponge.conf @@ -69,6 +69,10 @@ maps: [ # The path to the save-folder of the world to render world: "world" + + # The position on the world where the map will be centered if you open it. + # This defaults to the world-spawn if you don't set it. + #startPos: [500, -820] # If this is false, BlueMap tries to omit all blocks that are not visible from above-ground. # More specific: Block-Faces that have a sunlight/skylight value of 0 are removed.