diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8bc38db5..b43b9e16 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -9,10 +9,10 @@ jobs: - uses: actions/checkout@v2 with: submodules: recursive - - name: Set up JDK 1.8 + - name: Set up JDK 1.16 uses: actions/setup-java@v1 with: - java-version: 8 + java-version: 16 - name: Build with Gradle run: ./gradlew clean test build - uses: actions/upload-artifact@v2 diff --git a/BlueMapAPI b/BlueMapAPI index 6bdbdb15..c5450573 160000 --- a/BlueMapAPI +++ b/BlueMapAPI @@ -1 +1 @@ -Subproject commit 6bdbdb150db6ae0b2b4bf10fc9632e03367b310f +Subproject commit c5450573abc12bb23fcdb470f7fda58ab1a0676a diff --git a/BlueMapCommon/BlueMapVue b/BlueMapCommon/BlueMapVue index 4bef101c..df3676c5 160000 --- a/BlueMapCommon/BlueMapVue +++ b/BlueMapCommon/BlueMapVue @@ -1 +1 @@ -Subproject commit 4bef101c6ee6ddef23049287cc1a3736d954979e +Subproject commit df3676c5c9e58e41cb838b8c7c46309de5128246 diff --git a/BlueMapCommon/build.gradle b/BlueMapCommon/build.gradle index 2a3560cf..cccc0aa3 100644 --- a/BlueMapCommon/build.gradle +++ b/BlueMapCommon/build.gradle @@ -3,10 +3,10 @@ plugins { } dependencies { - compile 'com.mojang:brigadier:1.0.17' - - compile project(':BlueMapCore') - compile project(':BlueMapAPI') + api 'com.mojang:brigadier:1.0.17' + + api project(':BlueMapCore') + api project(':BlueMapAPI') testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' } @@ -34,8 +34,8 @@ task buildWebapp(type: NpmTask) { task zipWebapp(type: Zip) { dependsOn 'buildWebapp' from fileTree('BlueMapVue/dist/') - archiveName 'webapp.zip' - destinationDir(file('src/main/resources/de/bluecolored/bluemap/')) + archiveFileName.set('webapp.zip') + destinationDirectory.set(file('src/main/resources/de/bluecolored/bluemap/')) outputs.upToDateWhen { false } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java index 57c74978..ae7229b8 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java @@ -24,20 +24,18 @@ */ package de.bluecolored.bluemap.common; -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.common.web.WebSettings; import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.config.*; +import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.map.hires.RenderSettings; 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.WebSettings; import de.bluecolored.bluemap.core.world.SlicedWorld; import de.bluecolored.bluemap.core.world.World; import org.apache.commons.io.FileUtils; @@ -50,13 +48,14 @@ import java.util.*; /** * This is the attempt to generalize as many actions as possible to have CLI and Plugins run on the same general setup-code. */ +@DebugDump public class BlueMapService { - private MinecraftVersion minecraftVersion; - private File configFolder; - private ThrowingFunction worldUUIDProvider; - private ThrowingFunction worldNameProvider; + private final MinecraftVersion minecraftVersion; + private final File configFolder; + private final ThrowingFunction worldUUIDProvider; + private final ThrowingFunction worldNameProvider; - private ConfigManager configManager; + private final ConfigManager configManager; private CoreConfig coreConfig; private RenderConfig renderConfig; @@ -65,7 +64,7 @@ public class BlueMapService { private ResourcePack resourcePack; private Map worlds; - private Map maps; + private Map maps; public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) { this.minecraftVersion = minecraftVersion; @@ -105,17 +104,17 @@ public class BlueMapService { public synchronized WebSettings updateWebAppSettings() throws IOException, InterruptedException { WebSettings webSettings = new WebSettings(new File(getRenderConfig().getWebRoot(), "data" + File.separator + "settings.json")); webSettings.set(getRenderConfig().isUseCookies(), "useCookies"); + webSettings.set(getRenderConfig().isEnableFreeFlight(), "freeFlightEnabled"); webSettings.setAllMapsEnabled(false); - for (MapType map : getMaps().values()) { + for (BmMap map : getMaps().values()) { webSettings.setMapEnabled(true, map.getId()); - webSettings.setFrom(map.getTileRenderer(), map.getId()); - webSettings.setFrom(map.getWorld(), map.getId()); + webSettings.setFrom(map); } 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.setFrom(map); } webSettings.save(); @@ -127,7 +126,7 @@ public class BlueMapService { return worlds; } - public synchronized Map getMaps() throws IOException, InterruptedException { + public synchronized Map getMaps() throws IOException, InterruptedException { if (maps == null) loadWorldsAndMaps(); return maps; } @@ -135,6 +134,9 @@ public class BlueMapService { private synchronized void loadWorldsAndMaps() throws IOException, InterruptedException { maps = new HashMap<>(); worlds = new HashMap<>(); + + ConfigManager configManager = getConfigManager(); + configManager.loadResourceConfigs(configFolder, getResourcePack()); for (MapConfig mapConfig : getRenderConfig().getMapConfigs()) { String id = mapConfig.getId(); @@ -154,18 +156,15 @@ public class BlueMapService { continue; } - ConfigManager configManager = getConfigManager(); - configManager.loadResourceConfigs(configFolder, getResourcePack()); - World world = worlds.get(worldUUID); if (world == null) { try { - world = MCAWorld.load(worldFolder.toPath(), worldUUID, minecraftVersion, configManager.getBlockIdConfig(), configManager.getBlockPropertiesConfig(), configManager.getBiomeConfig(), worldNameProvider.apply(worldUUID), true); + world = MCAWorld.load(worldFolder.toPath(), worldUUID, minecraftVersion, configManager.getBlockIdConfig(), configManager.getBlockPropertiesConfig(), configManager.getBiomeConfig(), worldNameProvider.apply(worldUUID), mapConfig.isIgnoreMissingLightData()); 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); + Logger.global.logError("Failed to load map '" + id + "'!", e); continue; } } @@ -179,28 +178,20 @@ public class BlueMapService { 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"), + + BmMap map = new BmMap( + id, + name, + world, + getRenderConfig().getWebRoot().toPath().resolve("data").resolve(id), 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); + mapConfig + ); + + maps.put(id, map); } worlds = Collections.unmodifiableMap(worlds); @@ -209,7 +200,7 @@ public class BlueMapService { public synchronized ResourcePack getResourcePack() throws IOException, InterruptedException { if (resourcePack == null) { - File defaultResourceFile = new File(getCoreConfig().getDataFolder(), "minecraft-client-" + minecraftVersion.getVersionString() + ".jar"); + File defaultResourceFile = new File(getCoreConfig().getDataFolder(), "minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar"); File resourceExtensionsFile = new File(getCoreConfig().getDataFolder(), "resourceExtensions.zip"); File textureExportFile = new File(getRenderConfig().getWebRoot(), "data" + File.separator + "textures.json"); @@ -222,9 +213,9 @@ public class BlueMapService { //download file try { - Logger.global.logInfo("Downloading " + minecraftVersion.getClientDownloadUrl() + " to " + defaultResourceFile + " ..."); + Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ..."); FileUtils.forceMkdirParent(defaultResourceFile); - FileUtils.copyURLToFile(new URL(minecraftVersion.getClientDownloadUrl()), defaultResourceFile, 10000, 10000); + FileUtils.copyURLToFile(new URL(minecraftVersion.getResource().getClientUrl()), defaultResourceFile, 10000, 10000); } catch (IOException e) { throw new IOException("Failed to download resources!", e); } @@ -238,7 +229,7 @@ public class BlueMapService { if (resourceExtensionsFile.exists()) FileUtils.forceDelete(resourceExtensionsFile); FileUtils.forceMkdirParent(resourceExtensionsFile); - FileUtils.copyURLToFile(Plugin.class.getResource("/de/bluecolored/bluemap/" + minecraftVersion.getResourcePrefix() + "/resourceExtensions.zip"), resourceExtensionsFile, 10000, 10000); + FileUtils.copyURLToFile(Plugin.class.getResource("/de/bluecolored/bluemap/" + minecraftVersion.getResource().getResourcePrefix() + "/resourceExtensions.zip"), resourceExtensionsFile, 10000, 10000); //find more resource packs File[] resourcePacks = resourcePackFolder.listFiles(); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/InterruptableReentrantLock.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/InterruptableReentrantLock.java index a1264586..9fe3a0a7 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/InterruptableReentrantLock.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/InterruptableReentrantLock.java @@ -38,7 +38,7 @@ public class InterruptableReentrantLock extends ReentrantLock { } /** - * Aquires the lock and interrupts the currently holding thread if there is any. + * Acquires the lock and interrupts the currently holding thread if there is any. */ public void interruptAndLock() { while (!tryLock()) { diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java deleted file mode 100644 index 188d0b82..00000000 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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 com.flowpowered.math.vector.Vector2i; -import com.google.common.base.Preconditions; - -import de.bluecolored.bluemap.core.render.TileRenderer; -import de.bluecolored.bluemap.core.render.WorldTile; -import de.bluecolored.bluemap.core.world.World; - -public class MapType { - - private final String id; - private String name; - private World world; - private TileRenderer tileRenderer; - - public MapType(String id, String name, World world, TileRenderer tileRenderer) { - Preconditions.checkNotNull(id); - Preconditions.checkNotNull(name); - Preconditions.checkNotNull(world); - Preconditions.checkNotNull(tileRenderer); - - this.id = id; - this.name = name; - this.world = world; - this.tileRenderer = tileRenderer; - } - - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public World getWorld() { - return world; - } - - public TileRenderer getTileRenderer() { - return tileRenderer; - } - - public void renderTile(Vector2i tile) { - getTileRenderer().render(new WorldTile(getWorld(), tile)); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj != null && obj instanceof MapType) { - MapType that = (MapType) obj; - - return this.id.equals(that.id); - } - - return false; - } - -} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java deleted file mode 100644 index f391238d..00000000 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.flowpowered.math.vector.Vector2i; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.MultimapBuilder; - -import de.bluecolored.bluemap.core.logger.Logger; - -public class RenderManager { - - private volatile boolean running; - - private Thread[] renderThreads; - private ArrayDeque renderTickets; - private Map renderTicketMap; - private Deque renderTasks; - - public RenderManager(int threadCount) { - running = false; - renderThreads = new Thread[threadCount]; - renderTickets = new ArrayDeque<>(1000); - renderTicketMap = new HashMap<>(1000); - renderTasks = new ArrayDeque<>(); - } - - public synchronized void start() { - stop(); //ensure everything is stopped first - running = true; - - for (int i = 0; i < renderThreads.length; i++) { - renderThreads[i] = new Thread(this::renderThread); - renderThreads[i].setPriority(Thread.MIN_PRIORITY); - renderThreads[i].start(); - } - } - - public synchronized void stop() { - for (int i = 0; i < renderThreads.length; i++) { - if (renderThreads[i] != null) { - renderThreads[i].interrupt(); - } - } - - running = false; - } - - public void addRenderTask(RenderTask task) { - synchronized (renderTasks) { - renderTasks.add(task); - } - } - - public RenderTicket createTicket(MapType mapType, Vector2i tile) { - return createTicket(new RenderTicket(mapType, tile)); - } - - private RenderTicket createTicket(RenderTicket ticket) { - synchronized (renderTickets) { - if (renderTicketMap.putIfAbsent(ticket, ticket) == null) { - renderTickets.add(ticket); - return ticket; - } else { - return renderTicketMap.get(ticket); - } - } - } - - public Collection createTickets(MapType mapType, Collection tiles) { - if (tiles.size() < 0) return Collections.emptyList(); - - Collection tickets = new ArrayList<>(tiles.size()); - synchronized (renderTickets) { - for (Vector2i tile : tiles) { - tickets.add(createTicket(mapType, tile)); - } - } - - return tickets; - } - - public boolean prioritizeRenderTask(RenderTask renderTask) { - synchronized (renderTasks) { - if (renderTasks.remove(renderTask)) { - - //pause first task - RenderTask currentFirst = renderTasks.peek(); - if (currentFirst != null) currentFirst.pause(); - - renderTasks.addFirst(renderTask); - return true; - } - - return false; - } - } - - public boolean removeRenderTask(RenderTask renderTask) { - synchronized (renderTasks) { - return renderTasks.remove(renderTask); - } - } - - private void renderThread() { - RenderTicket ticket = null; - - while (running) { - synchronized (renderTickets) { - ticket = renderTickets.poll(); - if (ticket != null) renderTicketMap.remove(ticket); - } - - if (ticket == null) { - synchronized (renderTasks) { - RenderTask task = renderTasks.peek(); - if (task != null) { - ticket = task.poll(); - if (task.isFinished()) { - renderTasks.poll(); - task.getMapType().getTileRenderer().save(); - } - } - } - } - - if (ticket != null) { - try { - ticket.render(); - } catch (RuntimeException e) { - //catch possible runtime exceptions, display them, and wait a while .. then resurrect this render-thread - Logger.global.logError("Unexpected exception in render-thread!", e); - try { - Thread.sleep(10000); - } catch (InterruptedException interrupt) { Thread.currentThread().interrupt(); break; } - } - } else { - try { - Thread.sleep(1000); // we don't need a super fast response time, so waiting a second is totally fine - } catch (InterruptedException interrupt) { Thread.currentThread().interrupt(); break; } - } - - if (Thread.interrupted()) { - Thread.currentThread().interrupt(); - break; - } - } - } - - public int getQueueSize() { - return renderTickets.size(); - } - - public int getRenderThreadCount() { - return renderThreads.length; - } - - /** - * Returns a copy of the deque with the render tasks in order as array - */ - public RenderTask[] getRenderTasks(){ - synchronized (renderTasks) { - return renderTasks.toArray(new RenderTask[renderTasks.size()]); - } - } - - public int getRenderTaskCount(){ - return renderTasks.size(); - } - - public RenderTask getCurrentRenderTask() { - return renderTasks.peek(); - } - - public boolean isRunning() { - return running; - } - - public void writeState(DataOutputStream out) throws IOException { - //prepare renderTickets - ListMultimap tileMap = MultimapBuilder.hashKeys().arrayListValues().build(); - synchronized (renderTickets) { - for (RenderTicket ticket : renderTickets) { - tileMap.put(ticket.getMapType(), ticket.getTile()); - } - } - - //write renderTickets - Set maps = tileMap.keySet(); - out.writeInt(maps.size()); - for (MapType map : maps) { - List tiles = tileMap.get(map); - - out.writeUTF(map.getId()); - out.writeInt(tiles.size()); - for (Vector2i tile : tiles) { - out.writeInt(tile.getX()); - out.writeInt(tile.getY()); - } - } - - //write tasks - synchronized (renderTasks) { - out.writeInt(renderTasks.size()); - for (RenderTask task : renderTasks) { - task.write(out); - } - } - } - - public void readState(DataInputStream in, Collection mapTypes) throws IOException { - //read renderTickets - int mapCount = in.readInt(); - for (int i = 0; i < mapCount; i++) { - String mapId = in.readUTF(); - - MapType mapType = null; - for (MapType map : mapTypes) { - if (map.getId().equals(mapId)) { - mapType = map; - break; - } - } - if (mapType == null) { - Logger.global.logWarning("Some render-tickets can not be loaded because the map (id: '" + mapId + "') does not exist anymore. They will be discarded."); - } - - int tileCount = in.readInt(); - List tiles = new ArrayList<>(); - for (int j = 0; j < tileCount; j++) { - int x = in.readInt(); - int y = in.readInt(); - Vector2i tile = new Vector2i(x, y); - tiles.add(tile); - } - - if (mapType != null) createTickets(mapType, tiles); - } - - //read tasks - int taskCount = in.readInt(); - for (int i = 0; i < taskCount; i++) { - try { - RenderTask task = RenderTask.read(in, mapTypes); - addRenderTask(task); - } catch (IOException ex) { - Logger.global.logWarning("A render-task can not be loaded. It will be discared. (Error message: " + ex.toString() + ")"); - } - } - } - -} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTask.java deleted file mode 100644 index dddcdb17..00000000 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTask.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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 com.flowpowered.math.vector.Vector2d; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.*; - -public class RenderTask { - - private final UUID uuid; - private final String name; - - private final MapType mapType; - private final Deque renderTiles; - - private long firstTileTime; - private long additionalRunTime; - private int renderedTiles; - - public RenderTask(String name, MapType mapType) { - this.uuid = UUID.randomUUID(); - this.name = name; - this.mapType = mapType; - this.renderTiles = new ArrayDeque<>(); - this.firstTileTime = -1; - this.additionalRunTime = 0; - this.renderedTiles = 0; - } - - public void optimizeQueue(Vector2i centerBlockPos) { - //Find a good grid size to match the MCAWorlds chunk-cache size of 500 - Vector2d sortGridSize = new Vector2d(20, 20).div(mapType.getTileRenderer().getHiresModelManager().getTileSize().toDouble().div(16)).ceil().max(1, 1); - - Vector2i centerTile = mapType.getTileRenderer().getHiresModelManager().posToTile(new Vector3i(centerBlockPos.getX(), 0, centerBlockPos.getY())); - - synchronized (renderTiles) { - ArrayList tileList = new ArrayList<>(renderTiles); - tileList.sort((v1, v2) -> { - v1 = v1.sub(centerTile); - v2 = v2.sub(centerTile); - - Vector2i v1SortGridPos = v1.toDouble().div(sortGridSize).floor().toInt(); - Vector2i v2SortGridPos = v2.toDouble().div(sortGridSize).floor().toInt(); - - if (v1SortGridPos != v2SortGridPos){ - int v1Dist = v1SortGridPos.distanceSquared(Vector2i.ZERO); - int v2Dist = v2SortGridPos.distanceSquared(Vector2i.ZERO); - - if (v1Dist < v2Dist) return -1; - if (v1Dist > v2Dist) return 1; - - if (v1SortGridPos.getY() < v2SortGridPos.getY()) return -1; - if (v1SortGridPos.getY() > v2SortGridPos.getY()) return 1; - if (v1SortGridPos.getX() < v2SortGridPos.getX()) return -1; - if (v1SortGridPos.getX() > v2SortGridPos.getX()) return 1; - } - - if (v1.getY() < v2.getY()) return -1; - if (v1.getY() > v2.getY()) return 1; - if (v1.getX() < v2.getX()) return -1; - if (v1.getX() > v2.getX()) return 1; - - return 0; - }); - - renderTiles.clear(); - renderTiles.addAll(tileList); - } - } - - public void addTile(Vector2i tile) { - synchronized (renderTiles) { - renderTiles.add(tile); - } - } - - public void addTiles(Collection tiles) { - synchronized (renderTiles) { - renderTiles.addAll(tiles); - } - } - - public RenderTicket poll() { - synchronized (renderTiles) { - Vector2i tile = renderTiles.poll(); - if (tile != null) { - renderedTiles++; - if (firstTileTime < 0) firstTileTime = System.currentTimeMillis(); - return new RenderTicket(mapType, tile); - } else { - return null; - } - } - } - - /** - * Pauses the render-time counter. - * So if the rendering gets paused, the statistics remain correct. - * It will resume as soon as a new ticket gets polled - */ - public void pause() { - if (firstTileTime < 0) return; - - synchronized (renderTiles) { - additionalRunTime += System.currentTimeMillis() - firstTileTime; - firstTileTime = -1; - } - } - - public long getActiveTime() { - if (firstTileTime < 0) return additionalRunTime; - return (System.currentTimeMillis() - firstTileTime) + additionalRunTime; - } - - public UUID getUuid() { - return uuid; - } - - public String getName() { - return name; - } - - public MapType getMapType() { - return mapType; - } - - public int getRenderedTileCount() { - return renderedTiles; - } - - public int getRemainingTileCount() { - return renderTiles.size(); - } - - public boolean isFinished() { - return renderTiles.isEmpty(); - } - - public void write(DataOutputStream out) throws IOException { - synchronized (renderTiles) { - pause(); - - out.writeUTF(name); - out.writeUTF(mapType.getId()); - - out.writeLong(additionalRunTime); - out.writeInt(renderedTiles); - - out.writeInt(renderTiles.size()); - for (Vector2i tile : renderTiles) { - out.writeInt(tile.getX()); - out.writeInt(tile.getY()); - } - } - } - - public static RenderTask read(DataInputStream in, Collection mapTypes) throws IOException { - String name = in.readUTF(); - String mapId = in.readUTF(); - - MapType mapType = null; - for (MapType map : mapTypes) { - if (map.getId().equals(mapId)) { - mapType = map; - break; - } - } - if (mapType == null) throw new IOException("Map type with id '" + mapId + "' does not exist!"); - - RenderTask task = new RenderTask(name, mapType); - - task.additionalRunTime = in.readLong(); - task.renderedTiles = in.readInt(); - - int tileCount = in.readInt(); - List tiles = new ArrayList<>(); - for (int i = 0; i < tileCount; i++) { - int x = in.readInt(); - int y = in.readInt(); - Vector2i tile = new Vector2i(x, y); - tiles.add(tile); - } - - task.addTiles(tiles); - - return task; - } - -} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapAPIImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapAPIImpl.java index 26e1383f..00da738e 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapAPIImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapAPIImpl.java @@ -27,7 +27,7 @@ package de.bluecolored.bluemap.common.api; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.BlueMapWorld; -import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.common.api.marker.MarkerAPIImpl; import de.bluecolored.bluemap.common.api.render.RenderAPIImpl; import de.bluecolored.bluemap.common.plugin.Plugin; @@ -59,7 +59,7 @@ public class BlueMapAPIImpl extends BlueMapAPI { public BlueMapAPIImpl(Plugin plugin) { this.plugin = plugin; - this.renderer = new RenderAPIImpl(this, plugin.getRenderManager()); + this.renderer = new RenderAPIImpl(this, plugin); worlds = new HashMap<>(); for (World world : plugin.getWorlds()) { @@ -68,7 +68,7 @@ public class BlueMapAPIImpl extends BlueMapAPI { } maps = new HashMap<>(); - for (MapType map : plugin.getMapTypes()) { + for (BmMap map : plugin.getMapTypes()) { BlueMapMapImpl m = new BlueMapMapImpl(this, map); maps.put(m.getId(), m); } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapMapImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapMapImpl.java index 574df23c..dc244538 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapMapImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapMapImpl.java @@ -25,16 +25,19 @@ package de.bluecolored.bluemap.common.api; import com.flowpowered.math.vector.Vector2i; - import de.bluecolored.bluemap.api.BlueMapMap; -import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; +import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask; +import de.bluecolored.bluemap.core.map.BmMap; + +import java.util.function.Predicate; public class BlueMapMapImpl implements BlueMapMap { private BlueMapAPIImpl api; - private MapType delegate; + private BmMap delegate; - protected BlueMapMapImpl(BlueMapAPIImpl api, MapType delegate) { + protected BlueMapMapImpl(BlueMapAPIImpl api, BmMap delegate) { this.api = api; this.delegate = delegate; } @@ -56,15 +59,56 @@ public class BlueMapMapImpl implements BlueMapMap { @Override public Vector2i getTileSize() { - return delegate.getTileRenderer().getHiresModelManager().getTileSize(); + return delegate.getHiresModelManager().getTileGrid().getGridSize(); } @Override public Vector2i getTileOffset() { - return delegate.getTileRenderer().getHiresModelManager().getGridOrigin(); + return delegate.getHiresModelManager().getTileGrid().getOffset(); } - public MapType getMapType() { + @Override + public void setTileFilter(Predicate filter) { + delegate.setTileFilter(filter); + } + + @Override + public Predicate getTileFilter() { + return delegate.getTileFilter(); + } + + @Override + public boolean isFrozen() { + return !api.plugin.getPluginState().getMapState(delegate).isUpdateEnabled(); + } + + @Override + public synchronized void setFrozen(boolean frozen) { + if (isFrozen()) unfreeze(); + else freeze(); + } + + private synchronized void unfreeze() { + api.plugin.startWatchingMap(delegate); + api.plugin.getPluginState().getMapState(delegate).setUpdateEnabled(true); + api.plugin.getRenderManager().scheduleRenderTask(new MapUpdateTask(delegate)); + } + + private synchronized void freeze() { + api.plugin.stopWatchingMap(delegate); + api.plugin.getPluginState().getMapState(delegate).setUpdateEnabled(false); + api.plugin.getRenderManager().removeRenderTasksIf(task -> { + if (task instanceof MapUpdateTask) + return ((MapUpdateTask) task).getMap().equals(delegate); + + if (task instanceof WorldRegionRenderTask) + return ((WorldRegionRenderTask) task).getMap().equals(delegate); + + return false; + }); + } + + public BmMap getMapType() { return delegate; } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ExtrudeMarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ExtrudeMarkerImpl.java index 9a57bab6..61707d30 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ExtrudeMarkerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ExtrudeMarkerImpl.java @@ -26,15 +26,16 @@ package de.bluecolored.bluemap.common.api.marker; import com.flowpowered.math.vector.Vector2d; import com.flowpowered.math.vector.Vector3d; -import com.google.common.base.Preconditions; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.ExtrudeMarker; import de.bluecolored.bluemap.api.marker.Shape; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; import java.awt.*; import java.util.List; +import java.util.Objects; public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker { public static final String MARKER_TYPE = "extrude"; @@ -51,7 +52,7 @@ public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker public ExtrudeMarkerImpl(String id, BlueMapMap map, Vector3d position, Shape shape, float shapeMinY, float shapeMaxY) { super(id, map, position); - Preconditions.checkNotNull(shape); + Objects.requireNonNull(shape); this.shape = shape; this.shapeMinY = shapeMinY; @@ -85,7 +86,7 @@ public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker @Override public synchronized void setShape(Shape shape, float shapeMinY, float shapeMaxY) { - Preconditions.checkNotNull(shape); + Objects.requireNonNull(shape); this.shape = shape; this.shapeMinY = shapeMinY; @@ -122,7 +123,7 @@ public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker @Override public synchronized void setLineColor(Color color) { - Preconditions.checkNotNull(color); + Objects.requireNonNull(color); this.lineColor = color; this.hasUnsavedChanges = true; @@ -135,7 +136,7 @@ public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker @Override public synchronized void setFillColor(Color color) { - Preconditions.checkNotNull(color); + Objects.requireNonNull(color); this.fillColor = color; this.hasUnsavedChanges = true; @@ -148,32 +149,32 @@ public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker if (!overwriteChanges && hasUnsavedChanges) return; this.hasUnsavedChanges = false; - this.shape = readShape(markerNode.getNode("shape")); - this.shapeMinY = markerNode.getNode("shapeMinY").getFloat(0); - this.shapeMaxY = markerNode.getNode("shapeMaxY").getFloat(255); - this.depthTest = markerNode.getNode("depthTest").getBoolean(true); - this.lineWidth = markerNode.getNode("lineWidth").getInt(2); - this.lineColor = readColor(markerNode.getNode("lineColor")); - this.fillColor = readColor(markerNode.getNode("fillColor")); + this.shape = readShape(markerNode.node("shape")); + this.shapeMinY = markerNode.node("shapeMinY").getFloat(0); + this.shapeMaxY = (float) markerNode.node("shapeMaxY").getDouble(255); + this.depthTest = markerNode.node("depthTest").getBoolean(true); + this.lineWidth = markerNode.node("lineWidth").getInt(2); + this.lineColor = readColor(markerNode.node("lineColor")); + this.fillColor = readColor(markerNode.node("fillColor")); } @Override - public void save(ConfigurationNode markerNode) { + public void save(ConfigurationNode markerNode) throws SerializationException { super.save(markerNode); - writeShape(markerNode.getNode("shape"), this.shape); - markerNode.getNode("shapeMinY").setValue(Math.round(shapeMinY * 1000f) / 1000f); - markerNode.getNode("shapeMaxY").setValue(Math.round(shapeMaxY * 1000f) / 1000f); - markerNode.getNode("depthTest").setValue(this.depthTest); - markerNode.getNode("lineWidth").setValue(this.lineWidth); - writeColor(markerNode.getNode("lineColor"), this.lineColor); - writeColor(markerNode.getNode("fillColor"), this.fillColor); + writeShape(markerNode.node("shape"), this.shape); + markerNode.node("shapeMinY").set(Math.round(shapeMinY * 1000f) / 1000f); + markerNode.node("shapeMaxY").set(Math.round(shapeMaxY * 1000f) / 1000f); + markerNode.node("depthTest").set(this.depthTest); + markerNode.node("lineWidth").set(this.lineWidth); + writeColor(markerNode.node("lineColor"), this.lineColor); + writeColor(markerNode.node("fillColor"), this.fillColor); hasUnsavedChanges = false; } private Shape readShape(ConfigurationNode node) throws MarkerFileFormatException { - List posNodes = node.getChildrenList(); + List posNodes = node.childrenList(); if (posNodes.size() < 3) throw new MarkerFileFormatException("Failed to read shape: point-list has fewer than 3 entries!"); @@ -187,10 +188,10 @@ public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker private static Vector2d readShapePos(ConfigurationNode node) throws MarkerFileFormatException { ConfigurationNode nx, nz; - nx = node.getNode("x"); - nz = node.getNode("z"); + nx = node.node("x"); + nz = node.node("z"); - if (nx.isVirtual() || nz.isVirtual()) throw new MarkerFileFormatException("Failed to read shape position: Node x or z is not set!"); + if (nx.virtual() || nz.virtual()) throw new MarkerFileFormatException("Failed to read shape position: Node x or z is not set!"); return new Vector2d( nx.getDouble(), @@ -200,14 +201,14 @@ public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker private static Color readColor(ConfigurationNode node) throws MarkerFileFormatException { ConfigurationNode nr, ng, nb, na; - nr = node.getNode("r"); - ng = node.getNode("g"); - nb = node.getNode("b"); - na = node.getNode("a"); + nr = node.node("r"); + ng = node.node("g"); + nb = node.node("b"); + na = node.node("a"); - if (nr.isVirtual() || ng.isVirtual() || nb.isVirtual()) throw new MarkerFileFormatException("Failed to read color: Node r,g or b is not set!"); + if (nr.virtual() || ng.virtual() || nb.virtual()) throw new MarkerFileFormatException("Failed to read color: Node r,g or b is not set!"); - float alpha = na.getFloat(1); + float alpha = (float) na.getDouble(1); if (alpha < 0 || alpha > 1) throw new MarkerFileFormatException("Failed to read color: alpha value out of range (0-1)!"); try { @@ -217,25 +218,25 @@ public class ExtrudeMarkerImpl extends ObjectMarkerImpl implements ExtrudeMarker } } - private static void writeShape(ConfigurationNode node, Shape shape) { + private static void writeShape(ConfigurationNode node, Shape shape) throws SerializationException { for (int i = 0; i < shape.getPointCount(); i++) { ConfigurationNode pointNode = node.appendListNode(); Vector2d point = shape.getPoint(i); - pointNode.getNode("x").setValue(Math.round(point.getX() * 1000d) / 1000d); - pointNode.getNode("z").setValue(Math.round(point.getY() * 1000d) / 1000d); + pointNode.node("x").set(Math.round(point.getX() * 1000d) / 1000d); + pointNode.node("z").set(Math.round(point.getY() * 1000d) / 1000d); } } - private static void writeColor(ConfigurationNode node, Color color) { + private static void writeColor(ConfigurationNode node, Color color) throws SerializationException { int r = color.getRed(); int g = color.getGreen(); int b = color.getBlue(); float a = color.getAlpha() / 255f; - node.getNode("r").setValue(r); - node.getNode("g").setValue(g); - node.getNode("b").setValue(b); - node.getNode("a").setValue(a); + node.node("r").set(r); + node.node("g").set(g); + node.node("b").set(b); + node.node("a").set(a); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/HtmlMarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/HtmlMarkerImpl.java index 57b39855..0d5cf9b3 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/HtmlMarkerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/HtmlMarkerImpl.java @@ -29,7 +29,8 @@ import com.flowpowered.math.vector.Vector3d; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.HtmlMarker; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; public class HtmlMarkerImpl extends MarkerImpl implements HtmlMarker { public static final String MARKER_TYPE = "html"; @@ -82,30 +83,30 @@ public class HtmlMarkerImpl extends MarkerImpl implements HtmlMarker { if (!overwriteChanges && hasUnsavedChanges) return; this.hasUnsavedChanges = false; - this.html = markerNode.getNode("html").getString(""); - this.anchor = readAnchor(markerNode.getNode("anchor")); + this.html = markerNode.node("html").getString(""); + this.anchor = readAnchor(markerNode.node("anchor")); } @Override - public synchronized void save(ConfigurationNode markerNode) { + public synchronized void save(ConfigurationNode markerNode) throws SerializationException { super.save(markerNode); - markerNode.getNode("html").setValue(this.html); - writeAnchor(markerNode.getNode("anchor"), this.anchor); + markerNode.node("html").set(this.html); + writeAnchor(markerNode.node("anchor"), this.anchor); hasUnsavedChanges = false; } private static Vector2i readAnchor(ConfigurationNode node) { return new Vector2i( - node.getNode("x").getInt(0), - node.getNode("y").getInt(0) + node.node("x").getInt(0), + node.node("y").getInt(0) ); } - private static void writeAnchor(ConfigurationNode node, Vector2i anchor) { - node.getNode("x").setValue(anchor.getX()); - node.getNode("y").setValue(anchor.getY()); + private static void writeAnchor(ConfigurationNode node, Vector2i anchor) throws SerializationException { + node.node("x").set(anchor.getX()); + node.node("y").set(anchor.getY()); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/LineMarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/LineMarkerImpl.java index 59ff5b4e..528c5d27 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/LineMarkerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/LineMarkerImpl.java @@ -25,15 +25,16 @@ package de.bluecolored.bluemap.common.api.marker; import com.flowpowered.math.vector.Vector3d; -import com.google.common.base.Preconditions; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.Line; import de.bluecolored.bluemap.api.marker.LineMarker; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; import java.awt.*; import java.util.List; +import java.util.Objects; public class LineMarkerImpl extends ObjectMarkerImpl implements LineMarker { public static final String MARKER_TYPE = "line"; @@ -48,7 +49,7 @@ public class LineMarkerImpl extends ObjectMarkerImpl implements LineMarker { public LineMarkerImpl(String id, BlueMapMap map, Vector3d position, Line line) { super(id, map, position); - Preconditions.checkNotNull(line); + Objects.requireNonNull(line); this.line = line; this.lineWidth = 2; @@ -69,7 +70,7 @@ public class LineMarkerImpl extends ObjectMarkerImpl implements LineMarker { @Override public synchronized void setLine(Line line) { - Preconditions.checkNotNull(line); + Objects.requireNonNull(line); this.line = line; this.hasUnsavedChanges = true; @@ -104,7 +105,7 @@ public class LineMarkerImpl extends ObjectMarkerImpl implements LineMarker { @Override public synchronized void setLineColor(Color color) { - Preconditions.checkNotNull(color); + Objects.requireNonNull(color); this.lineColor = color; this.hasUnsavedChanges = true; @@ -117,27 +118,26 @@ public class LineMarkerImpl extends ObjectMarkerImpl implements LineMarker { if (!overwriteChanges && hasUnsavedChanges) return; this.hasUnsavedChanges = false; - this.line = readLine(markerNode.getNode("line")); - this.depthTest = markerNode.getNode("depthTest").getBoolean(true); - this.lineWidth = markerNode.getNode("lineWidth").getInt(2); - this.lineColor = readColor(markerNode.getNode("lineColor")); + this.line = readLine(markerNode.node("line")); + this.depthTest = markerNode.node("depthTest").getBoolean(true); + this.lineWidth = markerNode.node("lineWidth").getInt(2); + this.lineColor = readColor(markerNode.node("lineColor")); } @Override - public void save(ConfigurationNode markerNode) { + public void save(ConfigurationNode markerNode) throws SerializationException { super.save(markerNode); - writeLine(markerNode.getNode("line"), this.line); - markerNode.getNode("depthTest").setValue(this.depthTest); - markerNode.getNode("lineWidth").setValue(this.lineWidth); - writeColor(markerNode.getNode("lineColor"), this.lineColor); + writeLine(markerNode.node("line"), this.line); + markerNode.node("depthTest").set(this.depthTest); + markerNode.node("lineWidth").set(this.lineWidth); + writeColor(markerNode.node("lineColor"), this.lineColor); - hasUnsavedChanges = false; } private Line readLine(ConfigurationNode node) throws MarkerFileFormatException { - List posNodes = node.getChildrenList(); + List posNodes = node.childrenList(); if (posNodes.size() < 3) throw new MarkerFileFormatException("Failed to read line: point-list has fewer than 2 entries!"); @@ -151,11 +151,11 @@ public class LineMarkerImpl extends ObjectMarkerImpl implements LineMarker { private static Vector3d readLinePos(ConfigurationNode node) throws MarkerFileFormatException { ConfigurationNode nx, ny, nz; - nx = node.getNode("x"); - ny = node.getNode("y"); - nz = node.getNode("z"); + nx = node.node("x"); + ny = node.node("y"); + nz = node.node("z"); - if (nx.isVirtual() || ny.isVirtual() || nz.isVirtual()) throw new MarkerFileFormatException("Failed to read line position: Node x, y or z is not set!"); + if (nx.virtual() || ny.virtual() || nz.virtual()) throw new MarkerFileFormatException("Failed to read line position: Node x, y or z is not set!"); return new Vector3d( nx.getDouble(), @@ -166,14 +166,14 @@ public class LineMarkerImpl extends ObjectMarkerImpl implements LineMarker { private static Color readColor(ConfigurationNode node) throws MarkerFileFormatException { ConfigurationNode nr, ng, nb, na; - nr = node.getNode("r"); - ng = node.getNode("g"); - nb = node.getNode("b"); - na = node.getNode("a"); + nr = node.node("r"); + ng = node.node("g"); + nb = node.node("b"); + na = node.node("a"); - if (nr.isVirtual() || ng.isVirtual() || nb.isVirtual()) throw new MarkerFileFormatException("Failed to read color: Node r,g or b is not set!"); + if (nr.virtual() || ng.virtual() || nb.virtual()) throw new MarkerFileFormatException("Failed to read color: Node r,g or b is not set!"); - float alpha = na.getFloat(1); + float alpha = (float) na.getDouble(1); if (alpha < 0 || alpha > 1) throw new MarkerFileFormatException("Failed to read color: alpha value out of range (0-1)!"); try { @@ -183,26 +183,26 @@ public class LineMarkerImpl extends ObjectMarkerImpl implements LineMarker { } } - private static void writeLine(ConfigurationNode node, Line line) { + private static void writeLine(ConfigurationNode node, Line line) throws SerializationException { for (int i = 0; i < line.getPointCount(); i++) { ConfigurationNode pointNode = node.appendListNode(); Vector3d point = line.getPoint(i); - pointNode.getNode("x").setValue(Math.round(point.getX() * 1000d) / 1000d); - pointNode.getNode("y").setValue(Math.round(point.getY() * 1000d) / 1000d); - pointNode.getNode("z").setValue(Math.round(point.getZ() * 1000d) / 1000d); + pointNode.node("x").set(Math.round(point.getX() * 1000d) / 1000d); + pointNode.node("y").set(Math.round(point.getY() * 1000d) / 1000d); + pointNode.node("z").set(Math.round(point.getZ() * 1000d) / 1000d); } } - private static void writeColor(ConfigurationNode node, Color color) { + private static void writeColor(ConfigurationNode node, Color color) throws SerializationException { int r = color.getRed(); int g = color.getGreen(); int b = color.getBlue(); float a = color.getAlpha() / 255f; - node.getNode("r").setValue(r); - node.getNode("g").setValue(g); - node.getNode("b").setValue(b); - node.getNode("a").setValue(a); + node.node("r").set(r); + node.node("g").set(g); + node.node("b").set(b); + node.node("a").set(a); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerAPIImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerAPIImpl.java index c939236c..ef5df2a1 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerAPIImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerAPIImpl.java @@ -24,14 +24,13 @@ */ package de.bluecolored.bluemap.common.api.marker; -import com.google.common.collect.Sets; import de.bluecolored.bluemap.api.marker.MarkerAPI; import de.bluecolored.bluemap.api.marker.MarkerSet; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.util.FileUtils; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; import java.io.File; import java.io.IOException; @@ -50,7 +49,7 @@ public class MarkerAPIImpl implements MarkerAPI { this.markerFile = markerFile; this.markerSets = new ConcurrentHashMap<>(); - this.removedMarkerSets = Sets.newConcurrentHashSet(); + this.removedMarkerSets = Collections.newSetFromMap(new ConcurrentHashMap<>()); load(); } @@ -93,63 +92,67 @@ public class MarkerAPIImpl implements MarkerAPI { } private synchronized void load(boolean overwriteChanges) throws IOException { - Set externallyRemovedSets = new HashSet<>(markerSets.keySet()); + synchronized (MarkerAPIImpl.class) { + Set externallyRemovedSets = new HashSet<>(markerSets.keySet()); - if (markerFile.exists() && markerFile.isFile()) { - GsonConfigurationLoader loader = GsonConfigurationLoader.builder().setFile(markerFile).build(); - ConfigurationNode node = loader.load(); + if (markerFile.exists() && markerFile.isFile()) { + GsonConfigurationLoader loader = GsonConfigurationLoader.builder().file(markerFile).build(); + ConfigurationNode node = loader.load(); - for (ConfigurationNode markerSetNode : node.getNode("markerSets").getChildrenList()) { - String setId = markerSetNode.getNode("id").getString(); - if (setId == null) { - Logger.global.logDebug("Marker-API: Failed to load a markerset: No id defined!"); - continue; - } - - externallyRemovedSets.remove(setId); - if (!overwriteChanges && removedMarkerSets.contains(setId)) continue; - - MarkerSetImpl set = markerSets.get(setId); - - try { - if (set == null) { - set = new MarkerSetImpl(setId); - set.load(api, markerSetNode, true); - } else { - set.load(api, markerSetNode, overwriteChanges); + for (ConfigurationNode markerSetNode : node.node("markerSets").childrenList()) { + String setId = markerSetNode.node("id").getString(); + if (setId == null) { + Logger.global.logDebug("Marker-API: Failed to load a markerset: No id defined!"); + continue; + } + + externallyRemovedSets.remove(setId); + if (!overwriteChanges && removedMarkerSets.contains(setId)) continue; + + MarkerSetImpl set = markerSets.get(setId); + + try { + if (set == null) { + set = new MarkerSetImpl(setId); + set.load(api, markerSetNode, true); + } else { + set.load(api, markerSetNode, overwriteChanges); + } + markerSets.put(setId, set); + } catch (MarkerFileFormatException ex) { + Logger.global.logDebug("Marker-API: Failed to load marker-set '" + setId + ": " + ex); } - markerSets.put(setId, set); - } catch (MarkerFileFormatException ex) { - Logger.global.logDebug("Marker-API: Failed to load marker-set '" + setId + ": " + ex); } } - } - - if (overwriteChanges) { - for (String setId : externallyRemovedSets) { - markerSets.remove(setId); + + if (overwriteChanges) { + for (String setId : externallyRemovedSets) { + markerSets.remove(setId); + } + + removedMarkerSets.clear(); } - - removedMarkerSets.clear(); } } @Override public synchronized void save() throws IOException { - load(false); + synchronized (MarkerAPIImpl.class) { + load(false); - FileUtils.createFile(markerFile); + FileUtils.createFile(markerFile); - GsonConfigurationLoader loader = GsonConfigurationLoader.builder().setFile(markerFile).build(); - ConfigurationNode node = loader.createEmptyNode(); - - for (MarkerSetImpl set : markerSets.values()) { - set.save(node.getNode("markerSets").appendListNode()); + GsonConfigurationLoader loader = GsonConfigurationLoader.builder().file(markerFile).build(); + ConfigurationNode node = loader.createNode(); + + for (MarkerSetImpl set : markerSets.values()) { + set.save(node.node("markerSets").appendListNode()); + } + + loader.save(node); + + removedMarkerSets.clear(); } - - loader.save(node); - - removedMarkerSets.clear(); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java index 939f9328..2f31bdfa 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java @@ -25,12 +25,13 @@ package de.bluecolored.bluemap.common.api.marker; import com.flowpowered.math.vector.Vector3d; -import com.google.common.base.Preconditions; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.Marker; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; +import java.util.Objects; import java.util.Optional; public abstract class MarkerImpl implements Marker { @@ -45,9 +46,9 @@ public abstract class MarkerImpl implements Marker { private boolean hasUnsavedChanges; public MarkerImpl(String id, BlueMapMap map, Vector3d position) { - Preconditions.checkNotNull(id); - Preconditions.checkNotNull(map); - Preconditions.checkNotNull(position); + Objects.requireNonNull(id); + Objects.requireNonNull(map); + Objects.requireNonNull(position); this.id = id; this.map = map; @@ -151,46 +152,46 @@ public abstract class MarkerImpl implements Marker { hasUnsavedChanges = false; //map - String mapId = markerNode.getNode("map").getString(); + String mapId = markerNode.node("map").getString(); if (mapId == null) throw new MarkerFileFormatException("There is no map defined!"); this.map = api.getMap(mapId).orElseThrow(() -> new MarkerFileFormatException("Could not resolve map with id: " + mapId)); //position - this.postition = readPos(markerNode.getNode("position")); + this.postition = readPos(markerNode.node("position")); //minmaxDistance - this.minDistance = markerNode.getNode("minDistance").getDouble(0); - this.maxDistance = markerNode.getNode("maxDistance").getDouble(100000); + this.minDistance = markerNode.node("minDistance").getDouble(0); + this.maxDistance = markerNode.node("maxDistance").getDouble(100000); //label - this.label = markerNode.getNode("label").getString(this.id); + this.label = markerNode.node("label").getString(this.id); //link - this.link = markerNode.getNode("link").getString(); - this.newTab = markerNode.getNode("newTab").getBoolean(true); + this.link = markerNode.node("link").getString(); + this.newTab = markerNode.node("newTab").getBoolean(true); } - public synchronized void save(ConfigurationNode markerNode) { - markerNode.getNode("id").setValue(this.id); - markerNode.getNode("type").setValue(this.getType()); - markerNode.getNode("map").setValue(this.map.getId()); - writePos(markerNode.getNode("position"), this.postition); - markerNode.getNode("minDistance").setValue(Math.round(this.minDistance * 1000d) / 1000d); - markerNode.getNode("maxDistance").setValue(Math.round(this.maxDistance * 1000d) / 1000d); - markerNode.getNode("label").setValue(this.label); - markerNode.getNode("link").setValue(this.link); - markerNode.getNode("newTab").setValue(this.newTab); + public synchronized void save(ConfigurationNode markerNode) throws SerializationException { + markerNode.node("id").set(this.id); + markerNode.node("type").set(this.getType()); + markerNode.node("map").set(this.map.getId()); + writePos(markerNode.node("position"), this.postition); + markerNode.node("minDistance").set(Math.round(this.minDistance * 1000d) / 1000d); + markerNode.node("maxDistance").set(Math.round(this.maxDistance * 1000d) / 1000d); + markerNode.node("label").set(this.label); + markerNode.node("link").set(this.link); + markerNode.node("newTab").set(this.newTab); hasUnsavedChanges = false; } private static Vector3d readPos(ConfigurationNode node) throws MarkerFileFormatException { ConfigurationNode nx, ny, nz; - nx = node.getNode("x"); - ny = node.getNode("y"); - nz = node.getNode("z"); + nx = node.node("x"); + ny = node.node("y"); + nz = node.node("z"); - if (nx.isVirtual() || ny.isVirtual() || nz.isVirtual()) throw new MarkerFileFormatException("Failed to read position: One of the nodes x,y or z is missing!"); + if (nx.virtual() || ny.virtual() || nz.virtual()) throw new MarkerFileFormatException("Failed to read position: One of the nodes x,y or z is missing!"); return new Vector3d( nx.getDouble(), @@ -199,10 +200,10 @@ public abstract class MarkerImpl implements Marker { ); } - private static void writePos(ConfigurationNode node, Vector3d pos) { - node.getNode("x").setValue(Math.round(pos.getX() * 1000d) / 1000d); - node.getNode("y").setValue(Math.round(pos.getY() * 1000d) / 1000d); - node.getNode("z").setValue(Math.round(pos.getZ() * 1000d) / 1000d); + private static void writePos(ConfigurationNode node, Vector3d pos) throws SerializationException { + node.node("x").set(Math.round(pos.getX() * 1000d) / 1000d); + node.node("y").set(Math.round(pos.getY() * 1000d) / 1000d); + node.node("z").set(Math.round(pos.getZ() * 1000d) / 1000d); } @Override diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerSetImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerSetImpl.java index 3d400659..d8664250 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerSetImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerSetImpl.java @@ -25,7 +25,6 @@ package de.bluecolored.bluemap.common.api.marker; import com.flowpowered.math.vector.Vector3d; -import com.google.common.collect.Sets; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.Line; @@ -33,7 +32,8 @@ import de.bluecolored.bluemap.api.marker.Marker; import de.bluecolored.bluemap.api.marker.MarkerSet; import de.bluecolored.bluemap.api.marker.Shape; import de.bluecolored.bluemap.core.logger.Logger; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -57,7 +57,7 @@ public class MarkerSetImpl implements MarkerSet { this.isDefaultHidden = false; this.markers = new ConcurrentHashMap<>(); - this.removedMarkers = Sets.newConcurrentHashSet(); + this.removedMarkers = Collections.newSetFromMap(new ConcurrentHashMap<>()); this.hasUnsavedChanges = true; } @@ -175,9 +175,9 @@ public class MarkerSetImpl implements MarkerSet { Line dummyLine = new Line(Vector3d.ZERO, Vector3d.ONE); Set externallyRemovedMarkers = new HashSet<>(this.markers.keySet()); - for (ConfigurationNode markerNode : node.getNode("marker").getChildrenList()) { - String id = markerNode.getNode("id").getString(); - String type = markerNode.getNode("type").getString(); + for (ConfigurationNode markerNode : node.node("marker").childrenList()) { + String id = markerNode.node("id").getString(); + String type = markerNode.node("type").getString(); if (id == null || type == null) { Logger.global.logDebug("Marker-API: Failed to load a marker in the set '" + this.id + "': No id or type defined!"); @@ -238,19 +238,19 @@ public class MarkerSetImpl implements MarkerSet { if (!overwriteChanges && hasUnsavedChanges) return; hasUnsavedChanges = false; - this.label = node.getNode("label").getString(id); - this.toggleable = node.getNode("toggleable").getBoolean(true); - this.isDefaultHidden = node.getNode("defaultHide").getBoolean(false); + this.label = node.node("label").getString(id); + this.toggleable = node.node("toggleable").getBoolean(true); + this.isDefaultHidden = node.node("defaultHide").getBoolean(false); } - public synchronized void save(ConfigurationNode node) { - node.getNode("id").setValue(this.id); - node.getNode("label").setValue(this.label); - node.getNode("toggleable").setValue(this.toggleable); - node.getNode("defaultHide").setValue(this.isDefaultHidden); + public synchronized void save(ConfigurationNode node) throws SerializationException { + node.node("id").set(this.id); + node.node("label").set(this.label); + node.node("toggleable").set(this.toggleable); + node.node("defaultHide").set(this.isDefaultHidden); for (MarkerImpl marker : markers.values()) { - marker.save(node.getNode("marker").appendListNode()); + marker.save(node.node("marker").appendListNode()); } removedMarkers.clear(); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ObjectMarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ObjectMarkerImpl.java index 62a630f3..e45de772 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ObjectMarkerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ObjectMarkerImpl.java @@ -28,7 +28,8 @@ import com.flowpowered.math.vector.Vector3d; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.ObjectMarker; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; public abstract class ObjectMarkerImpl extends MarkerImpl implements ObjectMarker { @@ -63,14 +64,14 @@ public abstract class ObjectMarkerImpl extends MarkerImpl implements ObjectMarke if (!overwriteChanges && hasUnsavedChanges) return; this.hasUnsavedChanges = false; - this.detail = markerNode.getNode("detail").getString(); + this.detail = markerNode.node("detail").getString(); } @Override - public void save(ConfigurationNode markerNode) { + public void save(ConfigurationNode markerNode) throws SerializationException { super.save(markerNode); - if (this.detail != null) markerNode.getNode("detail").setValue(this.detail); + if (this.detail != null) markerNode.node("detail").set(this.detail); hasUnsavedChanges = false; } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/POIMarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/POIMarkerImpl.java index 6821a903..5e468560 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/POIMarkerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/POIMarkerImpl.java @@ -30,7 +30,8 @@ import com.flowpowered.math.vector.Vector3d; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.POIMarker; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; public class POIMarkerImpl extends MarkerImpl implements POIMarker { public static final String MARKER_TYPE = "poi"; @@ -78,33 +79,33 @@ public class POIMarkerImpl extends MarkerImpl implements POIMarker { if (!overwriteChanges && hasUnsavedChanges) return; this.hasUnsavedChanges = false; - this.iconAddress = markerNode.getNode("icon").getString("assets/poi.svg"); + this.iconAddress = markerNode.node("icon").getString("assets/poi.svg"); - ConfigurationNode anchorNode = markerNode.getNode("anchor"); - if (anchorNode.isVirtual()) anchorNode = markerNode.getNode("iconAnchor"); //fallback to deprecated "iconAnchor" + ConfigurationNode anchorNode = markerNode.node("anchor"); + if (anchorNode.virtual()) anchorNode = markerNode.node("iconAnchor"); //fallback to deprecated "iconAnchor" this.anchor = readAnchor(anchorNode); } @Override - public synchronized void save(ConfigurationNode markerNode) { + public synchronized void save(ConfigurationNode markerNode) throws SerializationException { super.save(markerNode); - markerNode.getNode("icon").setValue(this.iconAddress); - writeAnchor(markerNode.getNode("anchor"), this.anchor); + markerNode.node("icon").set(this.iconAddress); + writeAnchor(markerNode.node("anchor"), this.anchor); hasUnsavedChanges = false; } private static Vector2i readAnchor(ConfigurationNode node) { return new Vector2i( - node.getNode("x").getInt(0), - node.getNode("y").getInt(0) + node.node("x").getInt(0), + node.node("y").getInt(0) ); } - private static void writeAnchor(ConfigurationNode node, Vector2i anchor) { - node.getNode("x").setValue(anchor.getX()); - node.getNode("y").setValue(anchor.getY()); + private static void writeAnchor(ConfigurationNode node, Vector2i anchor) throws SerializationException { + node.node("x").set(anchor.getX()); + node.node("y").set(anchor.getY()); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ShapeMarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ShapeMarkerImpl.java index 80d13637..84c64808 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ShapeMarkerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ShapeMarkerImpl.java @@ -26,15 +26,16 @@ package de.bluecolored.bluemap.common.api.marker; import com.flowpowered.math.vector.Vector2d; import com.flowpowered.math.vector.Vector3d; -import com.google.common.base.Preconditions; import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.Shape; import de.bluecolored.bluemap.api.marker.ShapeMarker; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; import java.awt.*; import java.util.List; +import java.util.Objects; public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { public static final String MARKER_TYPE = "shape"; @@ -50,7 +51,7 @@ public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { public ShapeMarkerImpl(String id, BlueMapMap map, Vector3d position, Shape shape, float shapeY) { super(id, map, position); - Preconditions.checkNotNull(shape); + Objects.requireNonNull(shape); this.shape = shape; this.shapeY = shapeY; @@ -78,7 +79,7 @@ public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { @Override public synchronized void setShape(Shape shape, float shapeY) { - Preconditions.checkNotNull(shape); + Objects.requireNonNull(shape); this.shape = shape; this.shapeY = shapeY; @@ -114,7 +115,7 @@ public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { @Override public synchronized void setLineColor(Color color) { - Preconditions.checkNotNull(color); + Objects.requireNonNull(color); this.lineColor = color; this.hasUnsavedChanges = true; @@ -127,7 +128,7 @@ public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { @Override public synchronized void setFillColor(Color color) { - Preconditions.checkNotNull(color); + Objects.requireNonNull(color); this.fillColor = color; this.hasUnsavedChanges = true; @@ -140,34 +141,34 @@ public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { if (!overwriteChanges && hasUnsavedChanges) return; this.hasUnsavedChanges = false; - this.shape = readShape(markerNode.getNode("shape")); - this.shapeY = markerNode.getNode("shapeY").getFloat(markerNode.getNode("height").getFloat(64)); // fallback to deprecated "height" - this.depthTest = markerNode.getNode("depthTest").getBoolean(true); - this.lineWidth = markerNode.getNode("lineWidth").getInt(2); + this.shape = readShape(markerNode.node("shape")); + this.shapeY = (float) markerNode.node("shapeY").getDouble(markerNode.node("height").getDouble(64)); // fallback to deprecated "height" + this.depthTest = markerNode.node("depthTest").getBoolean(true); + this.lineWidth = markerNode.node("lineWidth").getInt(2); - ConfigurationNode lineColorNode = markerNode.getNode("lineColor"); - if (lineColorNode.isVirtual()) lineColorNode = markerNode.getNode("borderColor"); // fallback to deprecated "borderColor" + ConfigurationNode lineColorNode = markerNode.node("lineColor"); + if (lineColorNode.virtual()) lineColorNode = markerNode.node("borderColor"); // fallback to deprecated "borderColor" this.lineColor = readColor(lineColorNode); - this.fillColor = readColor(markerNode.getNode("fillColor")); + this.fillColor = readColor(markerNode.node("fillColor")); } @Override - public void save(ConfigurationNode markerNode) { + public void save(ConfigurationNode markerNode) throws SerializationException { super.save(markerNode); - writeShape(markerNode.getNode("shape"), this.shape); - markerNode.getNode("shapeY").setValue(Math.round(shapeY * 1000f) / 1000f); - markerNode.getNode("depthTest").setValue(this.depthTest); - markerNode.getNode("lineWidth").setValue(this.lineWidth); - writeColor(markerNode.getNode("lineColor"), this.lineColor); - writeColor(markerNode.getNode("fillColor"), this.fillColor); + writeShape(markerNode.node("shape"), this.shape); + markerNode.node("shapeY").set(Math.round(shapeY * 1000f) / 1000f); + markerNode.node("depthTest").set(this.depthTest); + markerNode.node("lineWidth").set(this.lineWidth); + writeColor(markerNode.node("lineColor"), this.lineColor); + writeColor(markerNode.node("fillColor"), this.fillColor); hasUnsavedChanges = false; } private Shape readShape(ConfigurationNode node) throws MarkerFileFormatException { - List posNodes = node.getChildrenList(); + List posNodes = node.childrenList(); if (posNodes.size() < 3) throw new MarkerFileFormatException("Failed to read shape: point-list has fewer than 3 entries!"); @@ -181,10 +182,10 @@ public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { private static Vector2d readShapePos(ConfigurationNode node) throws MarkerFileFormatException { ConfigurationNode nx, nz; - nx = node.getNode("x"); - nz = node.getNode("z"); + nx = node.node("x"); + nz = node.node("z"); - if (nx.isVirtual() || nz.isVirtual()) throw new MarkerFileFormatException("Failed to read shape position: Node x or z is not set!"); + if (nx.virtual() || nz.virtual()) throw new MarkerFileFormatException("Failed to read shape position: Node x or z is not set!"); return new Vector2d( nx.getDouble(), @@ -194,14 +195,14 @@ public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { private static Color readColor(ConfigurationNode node) throws MarkerFileFormatException { ConfigurationNode nr, ng, nb, na; - nr = node.getNode("r"); - ng = node.getNode("g"); - nb = node.getNode("b"); - na = node.getNode("a"); + nr = node.node("r"); + ng = node.node("g"); + nb = node.node("b"); + na = node.node("a"); - if (nr.isVirtual() || ng.isVirtual() || nb.isVirtual()) throw new MarkerFileFormatException("Failed to read color: Node r,g or b is not set!"); + if (nr.virtual() || ng.virtual() || nb.virtual()) throw new MarkerFileFormatException("Failed to read color: Node r,g or b is not set!"); - float alpha = na.getFloat(1); + float alpha = (float) na.getDouble(1); if (alpha < 0 || alpha > 1) throw new MarkerFileFormatException("Failed to read color: alpha value out of range (0-1)!"); try { @@ -211,25 +212,25 @@ public class ShapeMarkerImpl extends ObjectMarkerImpl implements ShapeMarker { } } - private static void writeShape(ConfigurationNode node, Shape shape) { + private static void writeShape(ConfigurationNode node, Shape shape) throws SerializationException { for (int i = 0; i < shape.getPointCount(); i++) { ConfigurationNode pointNode = node.appendListNode(); Vector2d point = shape.getPoint(i); - pointNode.getNode("x").setValue(Math.round(point.getX() * 1000d) / 1000d); - pointNode.getNode("z").setValue(Math.round(point.getY() * 1000d) / 1000d); + pointNode.node("x").set(Math.round(point.getX() * 1000d) / 1000d); + pointNode.node("z").set(Math.round(point.getY() * 1000d) / 1000d); } } - private static void writeColor(ConfigurationNode node, Color color) { + private static void writeColor(ConfigurationNode node, Color color) throws SerializationException { int r = color.getRed(); int g = color.getGreen(); int b = color.getBlue(); float a = color.getAlpha() / 255f; - node.getNode("r").setValue(r); - node.getNode("g").setValue(g); - node.getNode("b").setValue(b); - node.getNode("a").setValue(a); + node.node("r").set(r); + node.node("g").set(g); + node.node("b").set(b); + node.node("a").set(a); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/render/RenderAPIImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/render/RenderAPIImpl.java index f9dfdfd8..6693adb1 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/render/RenderAPIImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/render/RenderAPIImpl.java @@ -24,25 +24,34 @@ */ package de.bluecolored.bluemap.common.api.render; -import java.util.UUID; - import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.renderer.RenderAPI; -import de.bluecolored.bluemap.common.RenderManager; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.common.api.BlueMapMapImpl; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.rendermanager.MapPurgeTask; +import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; +import de.bluecolored.bluemap.common.rendermanager.RenderManager; +import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask; +import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.world.Grid; + +import java.io.IOException; +import java.util.Collection; +import java.util.UUID; public class RenderAPIImpl implements RenderAPI { - private BlueMapAPIImpl api; - private RenderManager renderManager; + private final BlueMapAPIImpl api; + private final Plugin plugin; + private final RenderManager renderManager; - public RenderAPIImpl(BlueMapAPIImpl api, RenderManager renderManager) { + public RenderAPIImpl(BlueMapAPIImpl api, Plugin plugin) { this.api = api; - this.renderManager = renderManager; + this.plugin = plugin; + this.renderManager = plugin.getRenderManager(); } @Override @@ -62,24 +71,42 @@ public class RenderAPIImpl implements RenderAPI { @Override public void render(BlueMapMap map, Vector2i tile) { - BlueMapMapImpl cmap; - if (map instanceof BlueMapMapImpl) { - cmap = (BlueMapMapImpl) map; - } else { - cmap = api.getMapForId(map.getId()); + BlueMapMapImpl cmap = castMap(map); + + Grid regionGrid = cmap.getWorld().getWorld().getRegionGrid(); + Grid tileGrid = cmap.getMapType().getHiresModelManager().getTileGrid(); + + for (Vector2i region : tileGrid.getIntersecting(tile, regionGrid)) { + renderManager.scheduleRenderTask(new WorldRegionRenderTask(cmap.getMapType(), region)); } - - renderManager.createTicket(cmap.getMapType(), tile); + } + + @Override + public boolean scheduleMapUpdateTask(BlueMapMap map, boolean force) { + BlueMapMapImpl cmap = castMap(map); + return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getMapType(), force)); + } + + @Override + public boolean scheduleMapUpdateTask(BlueMapMap map, Collection regions, boolean force) { + BlueMapMapImpl cmap = castMap(map); + return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getMapType(), regions, force)); + } + + @Override + public boolean scheduleMapPurgeTask(BlueMapMap map) throws IOException { + BlueMapMapImpl cmap = castMap(map); + return renderManager.scheduleRenderTask(new MapPurgeTask(cmap.getMapType())); } @Override public int renderQueueSize() { - return renderManager.getQueueSize(); + return renderManager.getScheduledRenderTasks().size(); } @Override public int renderThreadCount() { - return renderManager.getRenderThreadCount(); + return renderManager.getWorkerThreadCount(); } @Override @@ -89,12 +116,27 @@ public class RenderAPIImpl implements RenderAPI { @Override public void start() { - if (!isRunning()) renderManager.start(); + if (!isRunning()){ + renderManager.start(api.plugin.getCoreConfig().getRenderThreadCount()); + } + plugin.getPluginState().setRenderThreadsEnabled(true); } @Override public void pause() { renderManager.stop(); + plugin.getPluginState().setRenderThreadsEnabled(false); + } + + private BlueMapMapImpl castMap(BlueMapMap map) { + BlueMapMapImpl cmap; + if (map instanceof BlueMapMapImpl) { + cmap = (BlueMapMapImpl) map; + } else { + cmap = api.getMapForId(map.getId()); + } + + return cmap; } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateHandler.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateHandler.java deleted file mode 100644 index 941774a0..00000000 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateHandler.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.plugin; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Timer; -import java.util.TimerTask; -import java.util.UUID; - -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Multimap; -import com.google.common.collect.MultimapBuilder; - -import de.bluecolored.bluemap.common.MapType; -import de.bluecolored.bluemap.common.RenderManager; -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.core.world.World; - -public class MapUpdateHandler implements ServerEventListener { - - private Plugin plugin; - private Multimap updateBuffer; - private Timer flushTimer; - - public MapUpdateHandler(Plugin plugin) { - this.plugin = plugin; - updateBuffer = MultimapBuilder.hashKeys().hashSetValues().build(); - - flushTimer = new Timer("MapUpdateHandlerTimer", true); - } - - @Override - public void onWorldSaveToDisk(final UUID world) { - - // wait 5 sec before rendering so saving has finished - flushTimer.schedule(new TimerTask() { - @Override public void run() { flushUpdateBufferForWorld(world); } - }, 5000); - - } - - @Override - public void onChunkSaveToDisk(final UUID world, final Vector2i chunkPos) { - - // wait 5 sec before rendering so saving has finished - flushTimer.schedule(new TimerTask() { - @Override public void run() { flushUpdateBufferForChunk(world, chunkPos); } - }, 5000); - - } - - @Override - public void onBlockChange(UUID world, Vector3i blockPos) { - updateBlock(world, blockPos); - } - - @Override - public void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) { - int x = chunkPos.getX(); - int z = chunkPos.getY(); - - // also update the chunks around, because they might be modified or not rendered yet due to finalizations - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - updateChunk(world, new Vector2i(x + dx, z + dz)); - } - } - } - - private void updateChunk(UUID worldUUID, Vector2i chunkPos) { - World world = plugin.getWorld(worldUUID); - if (world == null) return; - - synchronized (updateBuffer) { - updateBuffer.put(worldUUID, chunkPos); - } - } - - private void updateBlock(UUID worldUUID, Vector3i pos){ - World world = plugin.getWorld(worldUUID); - if (world == null) return; - - synchronized (updateBuffer) { - Vector2i chunkPos = world.blockPosToChunkPos(pos); - updateBuffer.put(worldUUID, chunkPos); - } - } - - public int getUpdateBufferCount() { - return updateBuffer.size(); - } - - public void flushUpdateBuffer() { - RenderManager renderManager = plugin.getRenderManager(); - - synchronized (updateBuffer) { - for (MapType map : plugin.getMapTypes()) { - Collection chunks = updateBuffer.get(map.getWorld().getUUID()); - Collection tiles = new HashSet<>(chunks.size() * 2); - - for (Vector2i chunk : chunks) { - Vector3i min = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16); - Vector3i max = min.add(15, 255, 15); - - Vector3i xmin = new Vector3i(min.getX(), 0, max.getY()); - Vector3i xmax = new Vector3i(max.getX(), 255, min.getY()); - - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(min)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(max)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmin)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmax)); - } - - //invalidate caches of updated chunks - for (Vector2i chunk : chunks) { - map.getWorld().invalidateChunkCache(chunk); - } - - //create render-tickets - renderManager.createTickets(map, tiles); - } - - updateBuffer.clear(); - } - } - - public void flushUpdateBufferForWorld(UUID world) { - RenderManager renderManager = plugin.getRenderManager(); - - synchronized (updateBuffer) { - for (MapType map : plugin.getMapTypes()) { - if (!map.getWorld().getUUID().equals(world)) continue; - - Collection chunks = updateBuffer.get(world); - Collection tiles = new HashSet<>(chunks.size() * 2); - - for (Vector2i chunk : chunks) { - Vector3i min = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16); - Vector3i max = min.add(15, 255, 15); - - Vector3i xmin = new Vector3i(min.getX(), 0, max.getY()); - Vector3i xmax = new Vector3i(max.getX(), 255, min.getY()); - - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(min)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(max)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmin)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmax)); - } - - //invalidate caches of updated chunks - for (Vector2i chunk : chunks) { - map.getWorld().invalidateChunkCache(chunk); - } - - //create render-tickets - renderManager.createTickets(map, tiles); - } - - updateBuffer.removeAll(world); - } - } - - public void flushUpdateBufferForChunk(UUID world, Vector2i chunkPos) { - RenderManager renderManager = plugin.getRenderManager(); - - synchronized (updateBuffer) { - if (!updateBuffer.containsEntry(world, chunkPos)) return; - - for (MapType map : plugin.getMapTypes()) { - if (!map.getWorld().getUUID().equals(world)) continue; - - Collection tiles = new HashSet<>(4); - - Vector3i min = new Vector3i(chunkPos.getX() * 16, 0, chunkPos.getY() * 16); - Vector3i max = min.add(15, 255, 15); - - Vector3i xmin = new Vector3i(min.getX(), 0, max.getY()); - Vector3i xmax = new Vector3i(max.getX(), 255, min.getY()); - - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(min)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(max)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmin)); - tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmax)); - - //invalidate caches of updated chunk - map.getWorld().invalidateChunkCache(chunkPos); - - //create render-tickets - renderManager.createTickets(map, tiles); - } - - updateBuffer.remove(world, chunkPos); - } - } - -} 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 f7818ce4..4f64e598 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 @@ -24,31 +24,40 @@ */ package de.bluecolored.bluemap.common.plugin; -import de.bluecolored.bluemap.common.*; +import de.bluecolored.bluemap.common.BlueMapService; +import de.bluecolored.bluemap.common.InterruptableReentrantLock; +import de.bluecolored.bluemap.common.MissingResourcesException; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.common.plugin.skins.PlayerSkinUpdater; +import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; +import de.bluecolored.bluemap.common.rendermanager.RenderManager; +import de.bluecolored.bluemap.common.web.FileRequestHandler; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.config.CoreConfig; import de.bluecolored.bluemap.core.config.RenderConfig; import de.bluecolored.bluemap.core.config.WebServerConfig; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.debug.StateDumper; import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.metrics.Metrics; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.util.FileUtils; -import de.bluecolored.bluemap.core.web.FileRequestHandler; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; import de.bluecolored.bluemap.core.webserver.WebServer; import de.bluecolored.bluemap.core.world.World; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.serialize.SerializationException; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; +@DebugDump public class Plugin { public static final String PLUGIN_ID = "bluemap"; @@ -58,23 +67,28 @@ public class Plugin { private final MinecraftVersion minecraftVersion; private final String implementationType; - private ServerInterface serverInterface; + private final ServerInterface serverInterface; private BlueMapService blueMap; private BlueMapAPIImpl api; + private CoreConfig coreConfig; + private RenderConfig renderConfig; + private WebServerConfig webServerConfig; + private PluginConfig pluginConfig; + + private PluginState pluginState; + private Map worlds; - private Map maps; + private Map maps; private RenderManager renderManager; private WebServer webServer; - private final Timer daemonTimer; - private TimerTask periodicalSaveTask; - private TimerTask metricsTask; + private Timer daemonTimer; + + private Map regionFileWatchServices; - private PluginConfig pluginConfig; - private MapUpdateHandler updateHandler; private PlayerSkinUpdater skinUpdater; private boolean loaded = false; @@ -84,7 +98,7 @@ public class Plugin { this.implementationType = implementationType.toLowerCase(); this.serverInterface = serverInterface; - this.daemonTimer = new Timer("BlueMap-Daemon-Timer", true); + StateDumper.global().register(this); } public void load() throws IOException, ParseResourceException { @@ -98,19 +112,30 @@ public class Plugin { blueMap = new BlueMapService(minecraftVersion, serverInterface); //load configs - CoreConfig coreConfig = blueMap.getCoreConfig(); - RenderConfig renderConfig = blueMap.getRenderConfig(); - WebServerConfig webServerConfig = blueMap.getWebServerConfig(); + coreConfig = blueMap.getCoreConfig(); + renderConfig = blueMap.getRenderConfig(); + webServerConfig = blueMap.getWebServerConfig(); //load plugin config pluginConfig = new PluginConfig(blueMap.getConfigManager().loadOrCreate( - new File(serverInterface.getConfigFolder(), "plugin.conf"), - Plugin.class.getResource("/de/bluecolored/bluemap/plugin.conf"), - Plugin.class.getResource("/de/bluecolored/bluemap/plugin-defaults.conf"), + new File(serverInterface.getConfigFolder(), "plugin.conf"), + Plugin.class.getResource("/de/bluecolored/bluemap/plugin.conf"), + Plugin.class.getResource("/de/bluecolored/bluemap/plugin-defaults.conf"), true, true )); - + + //load plugin state + try { + GsonConfigurationLoader loader = GsonConfigurationLoader.builder() + .file(new File(getCoreConfig().getDataFolder(), "pluginState.json")) + .build(); + pluginState = loader.load().get(PluginState.class); + } catch (SerializationException ex) { + Logger.global.logWarning("Failed to load pluginState.json (invalid format), creating a new one..."); + pluginState = new PluginState(); + } + //create and start webserver if (webServerConfig.isWebserverEnabled()) { FileUtils.mkDirs(webServerConfig.getWebRoot()); @@ -151,48 +176,27 @@ public class Plugin { //warn if no maps are configured if (maps.isEmpty()) { Logger.global.logWarning("There are no valid maps configured, please check your render-config! Disabling BlueMap..."); + + unload(); + return; } //initialize render manager - renderManager = new RenderManager(coreConfig.getRenderThreadCount()); - renderManager.start(); - - //load render-manager state - try { - File saveFile = getRenderManagerSaveFile(); - if (saveFile.exists()) { - try (DataInputStream in = new DataInputStream(new GZIPInputStream(new FileInputStream(saveFile)))) { - renderManager.readState(in, getMapTypes()); - } - } - } catch (IOException ex) { - Logger.global.logError("Failed to load render-manager state!", ex); - } - - //do periodical saves - periodicalSaveTask = new TimerTask() { - @Override - public void run() { - try { - saveRenderManagerState(); + renderManager = new RenderManager(); - //clean up caches - for (World world : blueMap.getWorlds().values()) { - world.cleanUpChunkCache(); - } - } catch (IOException ex) { - Logger.global.logError("Failed to save render-manager state!", ex); - } catch (InterruptedException ex) { - this.cancel(); - Thread.currentThread().interrupt(); - } + //update all maps + for (BmMap map : maps.values()) { + if (pluginState.getMapState(map).isUpdateEnabled()) { + renderManager.scheduleRenderTask(new MapUpdateTask(map)); } - }; - daemonTimer.schedule(periodicalSaveTask, TimeUnit.MINUTES.toMillis(5), TimeUnit.MINUTES.toMillis(5)); - - //start map updater - this.updateHandler = new MapUpdateHandler(this); - serverInterface.registerListener(updateHandler); + } + + //start render-manager + if (pluginState.isRenderThreadsEnabled()) { + renderManager.start(coreConfig.getRenderThreadCount()); + } else { + Logger.global.logInfo("Render-Threads are STOPPED! Use the command 'bluemap start' to start them."); + } //update webapp and settings blueMap.createOrUpdateWebApp(false); @@ -206,9 +210,48 @@ public class Plugin { ); serverInterface.registerListener(skinUpdater); } - + + //init timer + daemonTimer = new Timer("BlueMap-Plugin-Daemon-Timer", true); + + //periodically save + TimerTask saveTask = new TimerTask() { + @Override + public void run() { + save(); + } + }; + daemonTimer.schedule(saveTask, TimeUnit.MINUTES.toMillis(2), TimeUnit.MINUTES.toMillis(2)); + + //periodically restart the file-watchers + TimerTask fileWatcherRestartTask = new TimerTask() { + @Override + public void run() { + regionFileWatchServices.values().forEach(RegionFileWatchService::close); + regionFileWatchServices.clear(); + initFileWatcherTasks(); + } + }; + daemonTimer.schedule(fileWatcherRestartTask, TimeUnit.HOURS.toMillis(1), TimeUnit.HOURS.toMillis(1)); + + //periodically update all (non frozen) maps + if (pluginConfig.getFullUpdateIntervalMinutes() > 0) { + long fullUpdateTime = TimeUnit.MINUTES.toMillis(pluginConfig.getFullUpdateIntervalMinutes()); + TimerTask updateAllMapsTask = new TimerTask() { + @Override + public void run() { + for (BmMap map : maps.values()) { + if (pluginState.getMapState(map).isUpdateEnabled()) { + renderManager.scheduleRenderTask(new MapUpdateTask(map)); + } + } + } + }; + daemonTimer.scheduleAtFixedRate(updateAllMapsTask, fullUpdateTime, fullUpdateTime); + } + //metrics - metricsTask = new TimerTask() { + TimerTask metricsTask = new TimerTask() { @Override public void run() { if (Plugin.this.serverInterface.isMetricsEnabled(coreConfig.isMetricsEnabled())) @@ -216,13 +259,17 @@ public class Plugin { } }; daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30)); - - loaded = true; - + + //watch map-changes + this.regionFileWatchServices = new HashMap<>(); + initFileWatcherTasks(); + //enable api this.api = new BlueMapAPIImpl(this); this.api.register(); - + + //done + loaded = true; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -236,6 +283,8 @@ public class Plugin { try { loadingLock.interruptAndLock(); synchronized (this) { + //save + save(); //disable api if (api != null) api.unregister(); @@ -243,84 +292,116 @@ public class Plugin { //unregister listeners serverInterface.unregisterAllListeners(); + skinUpdater = null; //stop scheduled threads - metricsTask.cancel(); - periodicalSaveTask.cancel(); - + if (daemonTimer != null) daemonTimer.cancel(); + daemonTimer = null; + + //stop file-watchers + if (regionFileWatchServices != null) { + regionFileWatchServices.values().forEach(RegionFileWatchService::close); + regionFileWatchServices.clear(); + } + regionFileWatchServices = null; + //stop services if (renderManager != null) renderManager.stop(); + renderManager = null; + if (webServer != null) webServer.close(); - - //save render-manager state - if (updateHandler != null) updateHandler.flushUpdateBuffer(); //first write all buffered changes to the render manager to save them too - if (renderManager != null) { - try { - saveRenderManagerState(); - } catch (IOException ex) { - Logger.global.logError("Failed to save render-manager state!", ex); - } - } - - //save renders - if (maps != null) { - for (MapType map : maps.values()) { - map.getTileRenderer().save(); - } - } - + webServer = null; + //clear resources and configs blueMap = null; worlds = null; maps = null; - renderManager = null; - webServer = null; - updateHandler = null; + + coreConfig = null; + renderConfig = null; + webServerConfig = null; pluginConfig = null; - + + pluginState = null; + + //done loaded = false; - } } finally { loadingLock.unlock(); } } - - public void saveRenderManagerState() throws IOException { - File saveFile = getRenderManagerSaveFile(); - - if (saveFile.exists()) FileUtils.delete(saveFile); - FileUtils.createFile(saveFile); - - try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) { - renderManager.writeState(out); - } - } public void reload() throws IOException, ParseResourceException { unload(); load(); } + + public synchronized void save() { + if (pluginState != null) { + try { + GsonConfigurationLoader loader = GsonConfigurationLoader.builder() + .file(new File(getCoreConfig().getDataFolder(), "pluginState.json")) + .build(); + loader.save(loader.createNode().set(PluginState.class, pluginState)); + } catch (IOException ex) { + Logger.global.logError("Failed to save pluginState.json!", ex); + } + } + + if (maps != null) { + for (BmMap map : maps.values()) { + map.save(); + } + } + } + + public synchronized void startWatchingMap(BmMap map) { + stopWatchingMap(map); + + try { + RegionFileWatchService watcher = new RegionFileWatchService(renderManager, map, false); + watcher.start(); + regionFileWatchServices.put(map.getId(), watcher); + } catch (IOException ex) { + Logger.global.logError("Failed to create file-watcher for map: " + map.getId() + " (This means the map might not automatically update)", ex); + } + } + + public synchronized void stopWatchingMap(BmMap map) { + RegionFileWatchService watcher = regionFileWatchServices.remove(map.getId()); + if (watcher != null) { + watcher.close(); + } + } + + public boolean flushWorldUpdates(UUID worldUUID) throws IOException { + return serverInterface.persistWorldChanges(worldUUID); + } public ServerInterface getServerInterface() { return serverInterface; } - public CoreConfig getCoreConfig() throws IOException { - return blueMap.getCoreConfig(); + public CoreConfig getCoreConfig() { + return coreConfig; } - public RenderConfig getRenderConfig() throws IOException { - return blueMap.getRenderConfig(); + public RenderConfig getRenderConfig() { + return renderConfig; } - public WebServerConfig getWebServerConfig() throws IOException { - return blueMap.getWebServerConfig(); + public WebServerConfig getWebServerConfig() { + return webServerConfig; } - + public PluginConfig getPluginConfig() { return pluginConfig; } + + public PluginState getPluginState() { + return pluginState; + } public World getWorld(UUID uuid){ return worlds.get(uuid); @@ -330,32 +411,14 @@ public class Plugin { return worlds.values(); } - public Collection getMapTypes(){ + public Collection getMapTypes(){ return maps.values(); } public RenderManager getRenderManager() { return renderManager; } - - public File getRenderManagerSaveFile() throws IOException { - if (blueMap == null) return null; - return new File(blueMap.getCoreConfig().getDataFolder(), "rmstate"); - } - - public MapUpdateHandler getUpdateHandler() { - return updateHandler; - } - - public boolean flushWorldUpdates(UUID worldUUID) throws IOException { - if (serverInterface.persistWorldChanges(worldUUID)) { - updateHandler.onWorldSaveToDisk(worldUUID); - return true; - } - - return false; - } - + public WebServer getWebServer() { return webServer; } @@ -371,5 +434,13 @@ public class Plugin { public MinecraftVersion getMinecraftVersion() { return minecraftVersion; } - + + private void initFileWatcherTasks() { + for (BmMap map : maps.values()) { + if (pluginState.getMapState(map).isUpdateEnabled()) { + startWatchingMap(map); + } + } + } + } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/PluginConfig.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/PluginConfig.java index 64f049b5..d59a0fc1 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/PluginConfig.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/PluginConfig.java @@ -24,12 +24,15 @@ */ package de.bluecolored.bluemap.common.plugin; +import de.bluecolored.bluemap.core.debug.DebugDump; +import org.spongepowered.configurate.ConfigurationNode; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.concurrent.TimeUnit; -import ninja.leaping.configurate.ConfigurationNode; - +@DebugDump public class PluginConfig { private boolean liveUpdatesEnabled = false; @@ -37,27 +40,31 @@ public class PluginConfig { private Collection hiddenGameModes = Collections.emptyList(); private boolean hideInvisible = false; private boolean hideSneaking = false; + private long fullUpdateIntervalMinutes = TimeUnit.HOURS.toMinutes(24); public PluginConfig(ConfigurationNode node) { //liveUpdates - liveUpdatesEnabled = node.getNode("liveUpdates").getBoolean(true); + liveUpdatesEnabled = node.node("liveUpdates").getBoolean(true); //skinDownloadEnabled - skinDownloadEnabled = node.getNode("skinDownload").getBoolean(true); + skinDownloadEnabled = node.node("skinDownload").getBoolean(true); //hiddenGameModes hiddenGameModes = new ArrayList<>(); - for (ConfigurationNode gameModeNode : node.getNode("hiddenGameModes").getChildrenList()) { + for (ConfigurationNode gameModeNode : node.node("hiddenGameModes").childrenList()) { hiddenGameModes.add(gameModeNode.getString()); } hiddenGameModes = Collections.unmodifiableCollection(hiddenGameModes); //hideInvisible - hideInvisible = node.getNode("hideInvisible").getBoolean(true); + hideInvisible = node.node("hideInvisible").getBoolean(true); //hideSneaking - hideSneaking = node.getNode("hideSneaking").getBoolean(false); + hideSneaking = node.node("hideSneaking").getBoolean(false); + + //periodic map updates + fullUpdateIntervalMinutes = node.node("fullUpdateInterval").getLong(TimeUnit.HOURS.toMinutes(24)); } @@ -80,5 +87,9 @@ public class PluginConfig { public boolean isHideSneaking() { return this.hideSneaking; } - + + public long getFullUpdateIntervalMinutes() { + return fullUpdateIntervalMinutes; + } + } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/PluginState.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/PluginState.java new file mode 100644 index 00000000..b1cefaa4 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/PluginState.java @@ -0,0 +1,67 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.plugin; + +import de.bluecolored.bluemap.core.map.BmMap; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("FieldMayBeFinal") +@ConfigSerializable +public class PluginState { + + private boolean renderThreadsEnabled = true; + private Map maps = new HashMap<>(); + + public boolean isRenderThreadsEnabled() { + return renderThreadsEnabled; + } + + public void setRenderThreadsEnabled(boolean renderThreadsEnabled) { + this.renderThreadsEnabled = renderThreadsEnabled; + } + + public MapState getMapState(BmMap map) { + return maps.computeIfAbsent(map.getId(), k -> new MapState()); + } + + @ConfigSerializable + public static class MapState { + + private boolean updateEnabled = true; + + public boolean isUpdateEnabled() { + return updateEnabled; + } + + public void setUpdateEnabled(boolean update) { + this.updateEnabled = update; + } + + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java new file mode 100644 index 00000000..d4123b52 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java @@ -0,0 +1,150 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.plugin; + +import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.common.rendermanager.RenderManager; +import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.map.BmMap; + +import java.io.IOException; +import java.nio.file.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +public class RegionFileWatchService extends Thread { + + private final BmMap map; + private final RenderManager renderManager; + private final WatchService watchService; + + private boolean verbose; + private volatile boolean closed; + + private Timer delayTimer; + + @DebugDump + private final Map scheduledUpdates; + + public RegionFileWatchService(RenderManager renderManager, BmMap map, boolean verbose) throws IOException { + this.renderManager = renderManager; + this.map = map; + this.verbose = verbose; + this.closed = false; + this.scheduledUpdates = new HashMap<>(); + + Path folder = map.getWorld().getSaveFolder().resolve("region"); + Files.createDirectories(folder); + + this.watchService = folder.getFileSystem().newWatchService(); + + folder.register(this.watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY); + } + + @Override + public void run() { + if (delayTimer == null) delayTimer = new Timer("BlueMap-RegionFileWatchService-DelayTimer", true); + + try { + while (!closed) { + WatchKey key = this.watchService.take(); + + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + + if (kind == StandardWatchEventKinds.OVERFLOW) continue; + + Object fileObject = event.context(); + if (!(fileObject instanceof Path)) continue; + Path file = (Path) fileObject; + + String regionFileName = file.toFile().getName(); + updateRegion(regionFileName); + } + + if (!key.reset()) return; + } + } catch ( ClosedWatchServiceException ignore) { + } catch (InterruptedException iex) { + Thread.currentThread().interrupt(); + } + + if (!closed) { + Logger.global.logWarning("Region-file watch-service for map '" + map.getId() + + "' stopped unexpectedly! (This map might not update automatically from now on)"); + } + } + + private synchronized void updateRegion(String regionFileName) { + if (!regionFileName.endsWith(".mca")) return; + if (!regionFileName.startsWith("r.")) return; + + try { + String[] filenameParts = regionFileName.split("\\."); + if (filenameParts.length < 3) return; + + int rX = Integer.parseInt(filenameParts[1]); + int rZ = Integer.parseInt(filenameParts[2]); + Vector2i regionPos = new Vector2i(rX, rZ); + + // we only want to start the render when there were no changes on a file for 10 seconds + TimerTask task = scheduledUpdates.remove(regionPos); + if (task != null) task.cancel(); + + task = new TimerTask() { + @Override + public void run() { + synchronized (RegionFileWatchService.this) { + WorldRegionRenderTask task = new WorldRegionRenderTask(map, regionPos); + scheduledUpdates.remove(regionPos); + renderManager.scheduleRenderTask(task); + + if (verbose) Logger.global.logInfo("Scheduled update for region-file: " + regionPos); + } + } + }; + scheduledUpdates.put(regionPos, task); + delayTimer.schedule(task, 10000); + } catch (NumberFormatException ignore) {} + } + + public void close() { + this.closed = true; + this.interrupt(); + + if (this.delayTimer != null) this.delayTimer.cancel(); + + try { + this.watchService.close(); + } catch (IOException ex) { + Logger.global.logError("Exception while trying to close WatchService!", ex); + } + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/AbstractSuggestionProvider.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/AbstractSuggestionProvider.java index f717ecca..6228087d 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/AbstractSuggestionProvider.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/AbstractSuggestionProvider.java @@ -24,9 +24,6 @@ */ package de.bluecolored.bluemap.common.plugin.commands; -import java.util.Collection; -import java.util.concurrent.CompletableFuture; - import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -34,6 +31,9 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + public abstract class AbstractSuggestionProvider implements SuggestionProvider { @Override diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/CommandHelper.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/CommandHelper.java index 3b33a5a7..c00a2597 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/CommandHelper.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/CommandHelper.java @@ -24,152 +24,95 @@ */ package de.bluecolored.bluemap.common.plugin.commands; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.StringJoiner; -import java.util.function.Predicate; - -import com.flowpowered.math.vector.Vector2l; -import org.apache.commons.lang3.time.DurationFormatUtils; - -import com.flowpowered.math.GenericMath; -import com.flowpowered.math.vector.Vector2i; - -import de.bluecolored.bluemap.common.MapType; -import de.bluecolored.bluemap.common.RenderManager; -import de.bluecolored.bluemap.common.RenderTask; import de.bluecolored.bluemap.common.plugin.Plugin; -import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; import de.bluecolored.bluemap.common.plugin.text.Text; import de.bluecolored.bluemap.common.plugin.text.TextColor; -import de.bluecolored.bluemap.common.plugin.text.TextFormat; -import de.bluecolored.bluemap.core.render.hires.HiresModelManager; +import de.bluecolored.bluemap.common.rendermanager.RenderManager; +import de.bluecolored.bluemap.common.rendermanager.RenderTask; +import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.world.World; +import org.apache.commons.lang3.time.DurationFormatUtils; + +import java.lang.ref.WeakReference; +import java.util.*; public class CommandHelper { - private Plugin plugin; + private final Plugin plugin; + private final Map> taskRefMap; public CommandHelper(Plugin plugin) { this.plugin = plugin; + this.taskRefMap = new HashMap<>(); } public List createStatusMessage(){ List lines = new ArrayList<>(); - + RenderManager renderer = plugin.getRenderManager(); - - lines.add(Text.of()); - lines.add(Text.of(TextColor.BLUE, "Tile-Updates:")); + List tasks = renderer.getScheduledRenderTasks(); + + lines.add(Text.of(TextColor.BLUE, "BlueMap - Status:")); if (renderer.isRunning()) { - lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", - Text.of(TextColor.GREEN, "running") - .setHoverText(Text.of("click to pause rendering")) - .setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap pause"), - TextColor.GRAY, "!")); + Text status; + if (tasks.isEmpty()) { + status = Text.of(TextColor.GRAY, "idle"); + } else { + status = Text.of(TextColor.GREEN, "running"); + } + + status.setHoverText(Text.of("click to stop rendering")); + status.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap stop"); + + lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", status, TextColor.WHITE, "!")); + + if (!tasks.isEmpty()) { + lines.add(Text.of(TextColor.WHITE, " Queued Tasks (" + tasks.size() + "):")); + for (int i = 0; i < tasks.size(); i++) { + if (i >= 10){ + lines.add(Text.of(TextColor.GRAY, "...")); + break; + } + + RenderTask task = tasks.get(i); + lines.add(Text.of(TextColor.GRAY, " [" + getRefForTask(task) + "] ", TextColor.GOLD, task.getDescription())); + + if (i == 0) { + lines.add(Text.of(TextColor.GRAY, " Progress: ", TextColor.WHITE, + (Math.round(task.estimateProgress() * 10000) / 100.0) + "%")); + + long etaMs = renderer.estimateCurrentRenderTaskTimeRemaining(); + if (etaMs > 0) { + lines.add(Text.of(TextColor.GRAY, " ETA: ", TextColor.WHITE, DurationFormatUtils.formatDuration(etaMs, "HH:mm:ss"))); + } + } + } + } } else { lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", - Text.of(TextColor.RED, "paused") - .setHoverText(Text.of("click to resume rendering")) - .setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap resume"), + Text.of(TextColor.RED, "stopped") + .setHoverText(Text.of("click to start rendering")) + .setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap start"), TextColor.GRAY, "!")); - } - - lines.add(Text.of( - TextColor.WHITE, " Scheduled tile-updates: ", - TextColor.GOLD, renderer.getQueueSize()).setHoverText( - Text.of( - TextColor.WHITE, "Tiles waiting for a free render-thread: ", TextColor.GOLD, renderer.getQueueSize(), - TextColor.WHITE, "\n\nChunks marked as changed: ", TextColor.GOLD, plugin.getUpdateHandler().getUpdateBufferCount(), - TextColor.GRAY, TextFormat.ITALIC, "\n(Changed chunks will be rendered as soon as they are saved back to the world-files)" - ) - ) - ); - - RenderTask[] tasks = renderer.getRenderTasks(); - if (tasks.length > 0) { - RenderTask task = tasks[0]; - - long time = task.getActiveTime(); - String durationString = DurationFormatUtils.formatDurationWords(time, true, true); - double pct = (double)task.getRenderedTileCount() / (double)(task.getRenderedTileCount() + task.getRemainingTileCount()); - - long ert = (long)((time / pct) * (1d - pct)); - String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true); - double tps = task.getRenderedTileCount() / (time / 1000.0); - - lines.add(Text.of(TextColor.BLUE, "Current task:")); - lines.add(Text.of(" ", createCancelTaskText(task), TextColor.WHITE, " Task ", TextColor.GOLD, task.getName(), TextColor.WHITE, " for map ", Text.of(TextColor.GOLD, task.getMapType().getName()).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GOLD, task.getMapType().getWorld().getName())))); - lines.add(Text.of(TextColor.WHITE, " rendered ", TextColor.GOLD, task.getRenderedTileCount(), TextColor.WHITE, " tiles ", TextColor.GRAY, "(" + (Math.round(pct * 1000)/10.0) + "% | " + GenericMath.round(tps, 1) + "t/s)", TextColor.WHITE, " in ", TextColor.GOLD, durationString)); - lines.add(Text.of(TextColor.WHITE, " with ", TextColor.GOLD, task.getRemainingTileCount(), TextColor.WHITE, " tiles to go. ETA: ", TextColor.GOLD, ertDurationString)); - } + if (!tasks.isEmpty()) { + lines.add(Text.of(TextColor.WHITE, " Queued Tasks (" + tasks.size() + "):")); + for (int i = 0; i < tasks.size(); i++) { + if (i >= 10){ + lines.add(Text.of(TextColor.GRAY, "...")); + break; + } - if (tasks.length > 1) { - lines.add(Text.of(TextColor.BLUE, "Waiting tasks:")); - for (int i = 1; i < tasks.length; i++) { - RenderTask task = tasks[i]; - lines.add(Text.of(" ", createCancelTaskText(task), createPrioritizeTaskText(task), TextColor.WHITE, " Task ", TextColor.GOLD, task.getName(), TextColor.WHITE, " for map ", Text.of(TextColor.GOLD, task.getMapType().getName()).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GOLD, task.getMapType().getWorld().getName())), TextColor.GRAY, " (" + task.getRemainingTileCount() + " tiles)")); + RenderTask task = tasks.get(i); + lines.add(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, task.getDescription())); + } } } - + return lines; } - private Text createCancelTaskText(RenderTask task) { - return Text.of(TextColor.RED, "[X]").setHoverText(Text.of(TextColor.GRAY, "click to cancel this render-task")).setClickAction(Text.ClickAction.RUN_COMMAND,"/bluemap render cancel " + task.getUuid()); - } - - private Text createPrioritizeTaskText(RenderTask task) { - return Text.of(TextColor.GREEN, "[^]").setHoverText(Text.of(TextColor.GRAY, "click to prioritize this render-task")).setClickAction(Text.ClickAction.RUN_COMMAND,"/bluemap render prioritize " + task.getUuid()); - } - - public void createWorldRenderTask(CommandSource source, World world, Vector2i center, long blockRadius) { - - for (MapType map : plugin.getMapTypes()) { - if (!map.getWorld().getUUID().equals(world.getUUID())) continue; - - createMapRenderTask(source, map, center, blockRadius); - } - - source.sendMessage(Text.of(TextColor.GREEN, "All render tasks created! Use /bluemap to view the progress!")); - } - - public void createMapRenderTask(CommandSource source, MapType map, Vector2i center, long blockRadius) { - source.sendMessage(Text.of(TextColor.GOLD, "Creating render-task for map: " + map.getId())); - source.sendMessage(Text.of(TextColor.GOLD, "Collecting chunks...")); - - String taskName = "world-render"; - Vector2i renderCenter = map.getWorld().getSpawnPoint().toVector2(true); - - Predicate filter; - if (center == null || blockRadius < 0) { - filter = c -> true; - } else { - Vector2l centerL = center.toLong(); //use longs to avoid int-overflow - filter = c -> c.toLong().mul(16).distanceSquared(centerL) <= blockRadius * blockRadius; - taskName = "radius-render"; - renderCenter = center; - } - - Collection chunks = map.getWorld().getChunkList(filter); - - source.sendMessage(Text.of(TextColor.GREEN, chunks.size() + " chunks found!")); - source.sendMessage(Text.of(TextColor.GOLD, "Collecting tiles...")); - - HiresModelManager hmm = map.getTileRenderer().getHiresModelManager(); - Collection tiles = hmm.getTilesForChunks(chunks); - - RenderTask task = new RenderTask(taskName, map); - task.addTiles(tiles); - task.optimizeQueue(renderCenter); - plugin.getRenderManager().addRenderTask(task); - - source.sendMessage(Text.of(TextColor.GREEN, tiles.size() + " tiles found! Task created.")); - } - public Text worldHelperHover() { StringJoiner joiner = new StringJoiner("\n"); for (World world : plugin.getWorlds()) { @@ -181,27 +124,45 @@ public class CommandHelper { public Text mapHelperHover() { StringJoiner joiner = new StringJoiner("\n"); - for (MapType map : plugin.getMapTypes()) { + for (BmMap map : plugin.getMapTypes()) { joiner.add(map.getId()); } return Text.of("map").setHoverText(Text.of(TextColor.WHITE, "Available maps: \n", TextColor.GRAY, joiner.toString())); } - - public boolean checkLoaded(CommandSource source) { - if (!plugin.isLoaded()) { - source.sendMessage(Text.of(TextColor.RED, "BlueMap is not loaded!", TextColor.GRAY, "(Try /bluemap reload)")); - return false; - } - - return true; + + public synchronized Optional getTaskForRef(String ref) { + return Optional.ofNullable(taskRefMap.get(ref)).map(WeakReference::get); } - public boolean checkPermission(CommandSource source, String permission) { - if (source.hasPermission(permission)) return true; - - source.sendMessage(Text.of(TextColor.RED, "You don't have the permissions to use this command!")); - return false; + public synchronized Collection getTaskRefs() { + return new ArrayList<>(taskRefMap.keySet()); } - + + private synchronized String getRefForTask(RenderTask task) { + Iterator>> iterator = taskRefMap.entrySet().iterator(); + while (iterator.hasNext()){ + Map.Entry> entry = iterator.next(); + if (entry.getValue().get() == null) iterator.remove(); + if (entry.getValue().get() == task) return entry.getKey(); + } + + String newRef = safeRandomRef(); + + taskRefMap.put(newRef, new WeakReference<>(task)); + return newRef; + } + + private synchronized String safeRandomRef() { + String ref = randomRef(); + while (taskRefMap.containsKey(ref)) ref = randomRef(); + return ref; + } + + private String randomRef() { + StringBuilder ref = new StringBuilder(Integer.toString(Math.abs(new Random().nextInt()), 16)); + while (ref.length() < 4) ref.insert(0, "0"); + return ref.subSequence(0, 4).toString(); + } + } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java index 0f9782c0..fb981649 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java @@ -24,51 +24,55 @@ */ package de.bluecolored.bluemap.common.plugin.commands; -import java.io.File; -import java.io.IOException; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; -import java.util.function.Predicate; - -import de.bluecolored.bluemap.common.plugin.text.TextFormat; -import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; -import org.apache.commons.io.FileUtils; - import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Lists; +import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.tree.LiteralCommandNode; - import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.marker.MarkerAPI; import de.bluecolored.bluemap.api.marker.MarkerSet; import de.bluecolored.bluemap.api.marker.POIMarker; -import de.bluecolored.bluemap.common.MapType; -import de.bluecolored.bluemap.common.RenderTask; import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.PluginState; import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; import de.bluecolored.bluemap.common.plugin.text.Text; import de.bluecolored.bluemap.common.plugin.text.TextColor; +import de.bluecolored.bluemap.common.plugin.text.TextFormat; +import de.bluecolored.bluemap.common.rendermanager.MapPurgeTask; +import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; +import de.bluecolored.bluemap.common.rendermanager.RenderTask; +import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask; +import de.bluecolored.bluemap.core.BlueMap; +import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.debug.StateDumper; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.mca.Chunk; +import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.map.MapRenderState; import de.bluecolored.bluemap.core.mca.ChunkAnvil112; +import de.bluecolored.bluemap.core.mca.MCAChunk; import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.World; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; + public class Commands { public static final String DEFAULT_MARKER_SET_ID = "markers"; @@ -136,66 +140,67 @@ public class Commands { .then(literal("cache") .executes(this::debugClearCacheCommand)) + + + .then(literal("dump") + .executes(this::debugDumpCommand)) .build(); - LiteralCommandNode pauseCommand = - literal("pause") - .requires(requirements("bluemap.pause")) - .executes(this::pauseCommand) + LiteralCommandNode stopCommand = + literal("stop") + .requires(requirements("bluemap.stop")) + .executes(this::stopCommand) .build(); - LiteralCommandNode resumeCommand = - literal("resume") - .requires(requirements("bluemap.resume")) - .executes(this::resumeCommand) + LiteralCommandNode startCommand = + literal("start") + .requires(requirements("bluemap.start")) + .executes(this::startCommand) .build(); - LiteralCommandNode renderCommand = - literal("render") - .requires(requirements("bluemap.render")) - .executes(this::renderCommand) // /bluemap render + LiteralCommandNode freezeCommand = + literal("freeze") + .requires(requirements("bluemap.freeze")) + .then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin)) + .executes(this::freezeCommand)) + .build(); - .then(argument("radius", IntegerArgumentType.integer()) - .executes(this::renderCommand)) // /bluemap render + LiteralCommandNode unfreezeCommand = + literal("unfreeze") + .requires(requirements("bluemap.freeze")) + .then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin)) + .executes(this::unfreezeCommand)) + .build(); - .then(argument("x", DoubleArgumentType.doubleArg()) - .then(argument("z", DoubleArgumentType.doubleArg()) - .then(argument("radius", IntegerArgumentType.integer()) - .executes(this::renderCommand)))) // /bluemap render + LiteralCommandNode forceUpdateCommand = + addRenderArguments( + literal("force-update") + .requires(requirements("bluemap.update.force")), + this::forceUpdateCommand + ).build(); - .then(argument("world|map", StringArgumentType.string()).suggests(new WorldOrMapSuggestionProvider<>(plugin)) - .executes(this::renderCommand) // /bluemap render - - .then(argument("x", DoubleArgumentType.doubleArg()) - .then(argument("z", DoubleArgumentType.doubleArg()) - .then(argument("radius", IntegerArgumentType.integer()) - .executes(this::renderCommand))))) // /bluemap render - .build(); - - LiteralCommandNode prioRenderCommand = - literal("prioritize") - .requires(requirements("bluemap.render")) - .then(argument("uuid", StringArgumentType.string()) - .executes(this::prioritizeRenderTaskCommand)) - .build(); - - LiteralCommandNode cancelRenderCommand = - literal("cancel") - .requires(requirements("bluemap.render")) - .executes(this::cancelLastRenderTaskCommand) - - .then(argument("uuid", StringArgumentType.string()) - .executes(this::cancelRenderTaskCommand)) - - .build(); + LiteralCommandNode updateCommand = + addRenderArguments( + literal("update") + .requires(requirements("bluemap.update")), + this::updateCommand + ).build(); LiteralCommandNode purgeCommand = literal("purge") - .requires(requirements("bluemap.render")) + .requires(requirements("bluemap.purge")) .then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin)) .executes(this::purgeCommand)) .build(); + + LiteralCommandNode cancelCommand = + literal("cancel") + .requires(requirements("bluemap.cancel")) + .executes(this::cancelCommand) + .then(argument("task-ref", StringArgumentType.string()).suggests(new TaskRefSuggestionProvider<>(helper)) + .executes(this::cancelCommand)) + .build(); LiteralCommandNode worldsCommand = literal("worlds") @@ -236,6 +241,12 @@ public class Commands { .then(argument("id", StringArgumentType.word()).suggests(MarkerIdSuggestionProvider.getInstance()) .executes(this::removeMarkerCommand)) .build(); + + LiteralCommandNode listMarkersCommand = + literal("list") + .requires(requirements("bluemap.marker")) + .executes(this::listMarkersCommand) + .build(); // command tree dispatcher.getRoot().addChild(baseCommand); @@ -243,17 +254,41 @@ public class Commands { baseCommand.addChild(helpCommand); baseCommand.addChild(reloadCommand); baseCommand.addChild(debugCommand); - baseCommand.addChild(pauseCommand); - baseCommand.addChild(resumeCommand); - baseCommand.addChild(renderCommand); + baseCommand.addChild(stopCommand); + baseCommand.addChild(startCommand); + baseCommand.addChild(freezeCommand); + baseCommand.addChild(unfreezeCommand); + baseCommand.addChild(forceUpdateCommand); + baseCommand.addChild(updateCommand); + baseCommand.addChild(cancelCommand); baseCommand.addChild(purgeCommand); - renderCommand.addChild(prioRenderCommand); - renderCommand.addChild(cancelRenderCommand); baseCommand.addChild(worldsCommand); baseCommand.addChild(mapsCommand); baseCommand.addChild(markerCommand); markerCommand.addChild(createMarkerCommand); markerCommand.addChild(removeMarkerCommand); + markerCommand.addChild(listMarkersCommand); + } + + private > B addRenderArguments(B builder, Command command) { + return builder + .executes(command) // /bluemap render + + .then(argument("radius", IntegerArgumentType.integer()) + .executes(command)) // /bluemap render + + .then(argument("x", DoubleArgumentType.doubleArg()) + .then(argument("z", DoubleArgumentType.doubleArg()) + .then(argument("radius", IntegerArgumentType.integer()) + .executes(command)))) // /bluemap render + + .then(argument("world|map", StringArgumentType.string()).suggests(new WorldOrMapSuggestionProvider<>(plugin)) + .executes(command) // /bluemap render + + .then(argument("x", DoubleArgumentType.doubleArg()) + .then(argument("z", DoubleArgumentType.doubleArg()) + .then(argument("radius", IntegerArgumentType.integer()) + .executes(command))))); // /bluemap render } private Predicate requirements(String permission){ @@ -296,8 +331,8 @@ public class Commands { return Optional.empty(); } - private Optional parseMap(String mapId) { - for (MapType map : plugin.getMapTypes()) { + private Optional parseMap(String mapId) { + for (BmMap map : plugin.getMapTypes()) { if (map.getId().equalsIgnoreCase(mapId)) { return Optional.of(map); } @@ -306,14 +341,6 @@ public class Commands { return Optional.empty(); } - private Optional parseUUID(String uuidString) { - try { - return Optional.of(UUID.fromString(uuidString)); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } - } - // --- COMMANDS --- @@ -334,21 +361,26 @@ public class Commands { int renderThreadCount = 0; if (plugin.isLoaded()) { - renderThreadCount = plugin.getRenderManager().getRenderThreadCount(); + renderThreadCount = plugin.getRenderManager().getWorkerThreadCount(); } source.sendMessage(Text.of(TextFormat.BOLD, TextColor.BLUE, "Version: ", TextColor.WHITE, BlueMap.VERSION)); + source.sendMessage(Text.of(TextColor.GRAY, "Commit: ", TextColor.WHITE, BlueMap.GIT_HASH + " (" + BlueMap.GIT_CLEAN + ")")); source.sendMessage(Text.of(TextColor.GRAY, "Implementation: ", TextColor.WHITE, plugin.getImplementationType())); - source.sendMessage(Text.of(TextColor.GRAY, "Minecraft compatibility: ", TextColor.WHITE, plugin.getMinecraftVersion().getVersionString())); + source.sendMessage(Text.of( + TextColor.GRAY, "Minecraft compatibility: ", TextColor.WHITE, plugin.getMinecraftVersion().getVersionString(), + TextColor.GRAY, " (" + plugin.getMinecraftVersion().getResource().getVersion().getVersionString() + ")" + )); source.sendMessage(Text.of(TextColor.GRAY, "Render-threads: ", TextColor.WHITE, renderThreadCount)); source.sendMessage(Text.of(TextColor.GRAY, "Available processors: ", TextColor.WHITE, Runtime.getRuntime().availableProcessors())); source.sendMessage(Text.of(TextColor.GRAY, "Available memory: ", TextColor.WHITE, (Runtime.getRuntime().maxMemory() / 1024L / 1024L) + " MiB")); - if (plugin.getMinecraftVersion().isAtLeast(MinecraftVersion.MC_1_15)) { + if (plugin.getMinecraftVersion().isAtLeast(new MinecraftVersion(1, 15))) { String clipboardValue = "Version: " + BlueMap.VERSION + "\n" + + "Commit: " + BlueMap.GIT_HASH + " (" + BlueMap.GIT_CLEAN + ")\n" + "Implementation: " + plugin.getImplementationType() + "\n" + - "Minecraft compatibility: " + plugin.getMinecraftVersion().getVersionString() + "\n" + + "Minecraft compatibility: " + plugin.getMinecraftVersion().getVersionString() + " (" + plugin.getMinecraftVersion().getResource().getVersion().getVersionString() + ")\n" + "Render-threads: " + renderThreadCount + "\n" + "Available processors: " + Runtime.getRuntime().availableProcessors() + "\n" + "Available memory: " + Runtime.getRuntime().maxMemory() / 1024L / 1024L + " MiB"; @@ -504,15 +536,14 @@ public class Commands { String blockBelowIdMeta = ""; if (world instanceof MCAWorld) { - Chunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos)); + MCAChunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos)); if (chunk instanceof ChunkAnvil112) { blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos) + ")"; blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos.add(0, -1, 0)) + ")"; } } - source.sendMessages(Lists.newArrayList( - Text.of(TextColor.GOLD, "Is generated: ", TextColor.WHITE, world.isChunkGenerated(world.blockPosToChunkPos(blockPos))), + source.sendMessages(Arrays.asList( Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block, TextColor.GRAY, blockIdMeta), Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow, TextColor.GRAY, blockBelowIdMeta) )); @@ -520,60 +551,171 @@ public class Commands { return 1; } + + public int debugDumpCommand(CommandContext context) { + final CommandSource source = commandSourceInterface.apply(context.getSource()); + + try { + Path file = plugin.getCoreConfig().getDataFolder().toPath().resolve("dump.json"); + StateDumper.global().dump(file); + + source.sendMessage(Text.of(TextColor.GREEN, "Dump created at: " + file)); + return 1; + } catch (IOException ex) { + Logger.global.logError("Failed to create dump!", ex); + source.sendMessage(Text.of(TextColor.RED, "Exception trying to create dump! See console for details.")); + return 0; + } + } - public int pauseCommand(CommandContext context) { + public int stopCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); if (plugin.getRenderManager().isRunning()) { - plugin.getRenderManager().stop(); - source.sendMessage(Text.of(TextColor.GREEN, "BlueMap rendering paused!")); - return 1; + new Thread(() -> { + plugin.getPluginState().setRenderThreadsEnabled(false); + + plugin.getRenderManager().stop(); + source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads stopped!")); + + plugin.save(); + }).start(); } else { - source.sendMessage(Text.of(TextColor.RED, "BlueMap rendering are already paused!")); + source.sendMessage(Text.of(TextColor.RED, "Render-Threads are already stopped!")); return 0; } + + return 1; } - public int resumeCommand(CommandContext context) { + public int startCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); if (!plugin.getRenderManager().isRunning()) { - plugin.getRenderManager().start(); - source.sendMessage(Text.of(TextColor.GREEN, "BlueMap renders resumed!")); - return 1; + new Thread(() -> { + plugin.getPluginState().setRenderThreadsEnabled(true); + + plugin.getRenderManager().start(plugin.getCoreConfig().getRenderThreadCount()); + source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads started!")); + + plugin.save(); + }).start(); } else { - source.sendMessage(Text.of(TextColor.RED, "BlueMap renders are already running!")); + source.sendMessage(Text.of(TextColor.RED, "Render-Threads are already running!")); return 0; } + + return 1; } - public int renderCommand(CommandContext context) { + public int freezeCommand(CommandContext context) { + CommandSource source = commandSourceInterface.apply(context.getSource()); + + // parse map argument + String mapString = context.getArgument("map", String.class); + BmMap map = parseMap(mapString).orElse(null); + + if (map == null) { + source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString)); + return 0; + } + + PluginState.MapState mapState = plugin.getPluginState().getMapState(map); + if (mapState.isUpdateEnabled()) { + new Thread(() -> { + mapState.setUpdateEnabled(false); + + plugin.stopWatchingMap(map); + plugin.getRenderManager().removeRenderTasksIf(task -> { + if (task instanceof MapUpdateTask) + return ((MapUpdateTask) task).getMap().equals(map); + + if (task instanceof WorldRegionRenderTask) + return ((WorldRegionRenderTask) task).getMap().equals(map); + + return false; + }); + + source.sendMessage(Text.of(TextColor.GREEN, "Map ", TextColor.WHITE, mapString, TextColor.GREEN, " is now frozen and will no longer be automatically updated!")); + source.sendMessage(Text.of(TextColor.GRAY, "Any currently scheduled updates for this map have been cancelled.")); + + plugin.save(); + }).start(); + } else { + source.sendMessage(Text.of(TextColor.RED, "This map is already frozen!")); + return 0; + } + + return 1; + } + + public int unfreezeCommand(CommandContext context) { + CommandSource source = commandSourceInterface.apply(context.getSource()); + + // parse map argument + String mapString = context.getArgument("map", String.class); + BmMap map = parseMap(mapString).orElse(null); + + if (map == null) { + source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString)); + return 0; + } + + PluginState.MapState mapState = plugin.getPluginState().getMapState(map); + if (!mapState.isUpdateEnabled()) { + new Thread(() -> { + mapState.setUpdateEnabled(true); + + plugin.startWatchingMap(map); + plugin.getRenderManager().scheduleRenderTask(new MapUpdateTask(map)); + + source.sendMessage(Text.of(TextColor.GREEN, "Map ", TextColor.WHITE, mapString, TextColor.GREEN, " is no longer frozen and will be automatically updated!")); + + plugin.save(); + }).start(); + } else { + source.sendMessage(Text.of(TextColor.RED, "This map is not frozen!")); + return 0; + } + + return 1; + } + + public int forceUpdateCommand(CommandContext context) { + return updateCommand(context, true); + } + + public int updateCommand(CommandContext context) { + return updateCommand(context, false); + } + + public int updateCommand(CommandContext context, boolean force) { final CommandSource source = commandSourceInterface.apply(context.getSource()); // parse world/map argument Optional worldOrMap = getOptionalArgument(context, "world|map", String.class); - final World world; - final MapType map; + final World worldToRender; + final BmMap mapToRender; if (worldOrMap.isPresent()) { - world = parseWorld(worldOrMap.get()).orElse(null); + worldToRender = parseWorld(worldOrMap.get()).orElse(null); - if (world == null) { - map = parseMap(worldOrMap.get()).orElse(null); + if (worldToRender == null) { + mapToRender = parseMap(worldOrMap.get()).orElse(null); - if (map == 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())); return 0; } } else { - map = null; + mapToRender = null; } } else { - world = source.getWorld().orElse(null); - map = null; + worldToRender = source.getWorld().orElse(null); + mapToRender = null; - if (world == 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 render!").setHoverText(Text.of(TextColor.GRAY, "/bluemap render "))); + 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") + " "))); return 0; } } @@ -590,7 +732,7 @@ public class Commands { } else { Vector3d position = source.getPosition().orElse(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 render with a radius!").setHoverText(Text.of(TextColor.GRAY, "/bluemap render " + 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") + " " + radius))); return 0; } @@ -603,13 +745,30 @@ public class Commands { // execute render new Thread(() -> { try { - if (world != null) { - plugin.getServerInterface().persistWorldChanges(world.getUUID()); - helper.createWorldRenderTask(source, world, center, radius); + List maps = new ArrayList<>(); + if (worldToRender != null) { + plugin.getServerInterface().persistWorldChanges(worldToRender.getUUID()); + for (BmMap map : plugin.getMapTypes()) { + if (map.getWorld().equals(worldToRender)) maps.add(map); + } } else { - plugin.getServerInterface().persistWorldChanges(map.getWorld().getUUID()); - helper.createMapRenderTask(source, map, center, radius); + plugin.getServerInterface().persistWorldChanges(mapToRender.getWorld().getUUID()); + maps.add(mapToRender); } + + for (BmMap map : maps) { + MapUpdateTask updateTask = new MapUpdateTask(map, center, radius); + plugin.getRenderManager().scheduleRenderTask(updateTask); + + if (force) { + MapRenderState state = map.getRenderState(); + 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, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress.")); + } catch (IOException ex) { source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to save the world. Please check the console for more details...")); Logger.global.logError("Unexpected exception trying to save the world!", ex); @@ -618,6 +777,34 @@ public class Commands { return 1; } + + public int cancelCommand(CommandContext context) { + CommandSource source = commandSourceInterface.apply(context.getSource()); + + Optional ref = getOptionalArgument(context,"task-ref", String.class); + if (!ref.isPresent()) { + plugin.getRenderManager().removeAllRenderTasks(); + source.sendMessage(Text.of(TextColor.GREEN, "All tasks cancelled!")); + source.sendMessage(Text.of(TextColor.GRAY, "(Note, that an already started task might not be removed immediately. Some tasks needs to do some tidying-work first)")); + return 1; + } + + Optional task = helper.getTaskForRef(ref.get()); + + if (!task.isPresent()) { + source.sendMessage(Text.of(TextColor.RED, "There is no task with this reference '" + ref.get() + "'!")); + return 0; + } + + if (plugin.getRenderManager().removeRenderTask(task.get())) { + source.sendMessage(Text.of(TextColor.GREEN, "Task cancelled!")); + source.sendMessage(Text.of(TextColor.GRAY, "(Note, that an already started task might not be removed immediately. Some tasks needs to do some tidying-work first)")); + return 1; + } else { + source.sendMessage(Text.of(TextColor.RED, "This task is either completed or got cancelled already!")); + return 0; + } + } public int purgeCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); @@ -627,14 +814,34 @@ public class Commands { new Thread(() -> { try { - File mapFolder = new File(plugin.getRenderConfig().getWebRoot(), "data" + File.separator + mapId); - if (!mapFolder.exists() || !mapFolder.isDirectory()) { + Path mapFolder = plugin.getRenderConfig().getWebRoot().toPath().resolve("data").resolve(mapId); + if (!Files.isDirectory(mapFolder)) { source.sendMessage(Text.of(TextColor.RED, "There is no map-data to purge for the map-id '" + mapId + "'!")); return; } - - FileUtils.deleteDirectory(mapFolder); - source.sendMessage(Text.of(TextColor.GREEN, "Map '" + mapId + "' has been successfully purged!")); + + Optional optMap = parseMap(mapId); + + // delete map + MapPurgeTask purgeTask; + if (optMap.isPresent()){ + purgeTask = new MapPurgeTask(optMap.get()); + } else { + purgeTask = new MapPurgeTask(mapFolder); + } + + plugin.getRenderManager().scheduleRenderTaskNext(purgeTask); + source.sendMessage(Text.of(TextColor.GREEN, "Created new Task to purge map '" + mapId + "'")); + + // if map is loaded, reset it and start updating it after the purge + if (optMap.isPresent()) { + RenderTask updateTask = new MapUpdateTask(optMap.get()); + plugin.getRenderManager().scheduleRenderTask(updateTask); + source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + mapId + "'")); + source.sendMessage(Text.of(TextColor.GRAY, "If you don't want to render this map again, you need to remove it from your configuration first!")); + } + + source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress.")); } catch (IOException | IllegalArgumentException e) { source.sendMessage(Text.of(TextColor.RED, "There was an error trying to purge '" + mapId + "', see console for details.")); Logger.global.logError("Failed to purge map '" + mapId + "'!", e); @@ -644,68 +851,6 @@ public class Commands { return 1; } - public int prioritizeRenderTaskCommand(CommandContext context) { - CommandSource source = commandSourceInterface.apply(context.getSource()); - - String uuidString = context.getArgument("uuid", String.class); - Optional taskUUID = parseUUID(uuidString); - if (!taskUUID.isPresent()) { - source.sendMessage(Text.of(TextColor.RED, "Not a valid UUID: " + uuidString)); - return 0; - } - - for (RenderTask task : plugin.getRenderManager().getRenderTasks()) { - if (task.getUuid().equals(taskUUID.get())) { - plugin.getRenderManager().prioritizeRenderTask(task); - - source.sendMessages(helper.createStatusMessage()); - return 1; - } - } - - source.sendMessage(Text.of(TextColor.RED, "There is no render-task with this UUID: " + uuidString)); - return 0; - } - - public int cancelLastRenderTaskCommand(CommandContext context) { - CommandSource source = commandSourceInterface.apply(context.getSource()); - - RenderTask[] tasks = plugin.getRenderManager().getRenderTasks(); - if (tasks.length == 0) { - source.sendMessage(Text.of(TextColor.RED, "There is currently no render task scheduled!")); - return 0; - } - - RenderTask task = tasks[tasks.length - 1]; - - plugin.getRenderManager().removeRenderTask(task); - source.sendMessage(Text.of(TextColor.GREEN, "The render-task '" + task.getName() + "' has been canceled!")); - return 1; - } - - public int cancelRenderTaskCommand(CommandContext context) { - CommandSource source = commandSourceInterface.apply(context.getSource()); - - String uuidString = context.getArgument("uuid", String.class); - Optional taskUUID = parseUUID(uuidString); - if (!taskUUID.isPresent()) { - source.sendMessage(Text.of(TextColor.RED, "Not a valid UUID: " + uuidString)); - return 0; - } - - for (RenderTask task : plugin.getRenderManager().getRenderTasks()) { - if (task.getUuid().equals(taskUUID.get())) { - plugin.getRenderManager().removeRenderTask(task); - - source.sendMessages(helper.createStatusMessage()); - return 1; - } - } - - source.sendMessage(Text.of(TextColor.RED, "There is no render-task with this UUID: " + uuidString)); - return 0; - } - public int worldsCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); @@ -721,8 +866,22 @@ public class Commands { CommandSource source = commandSourceInterface.apply(context.getSource()); source.sendMessage(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:")); - for (MapType map : plugin.getMapTypes()) { - source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, map.getId(), TextColor.GRAY, " (" + map.getName() + ")").setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName()))); + for (BmMap map : plugin.getMapTypes()) { + boolean unfrozen = plugin.getPluginState().getMapState(map).isUpdateEnabled(); + if (unfrozen) { + source.sendMessage(Text.of( + TextColor.GRAY, " - ", + TextColor.WHITE, map.getId(), + TextColor.GRAY, " (" + map.getName() + ")" + ).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName()))); + } else { + source.sendMessage(Text.of( + TextColor.GRAY, " - ", + TextColor.WHITE, map.getId(), + TextColor.GRAY, " (" + map.getName() + ") - ", + TextColor.AQUA, TextFormat.ITALIC, "frozen!" + ).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName()))); + } } return 1; @@ -736,9 +895,9 @@ public class Commands { .replace("<", "<") .replace(">", ">"); //no html via commands - // parse world/map argument + // parse map argument String mapString = context.getArgument("map", String.class); - MapType map = parseMap(mapString).orElse(null); + BmMap map = parseMap(mapString).orElse(null); if (map == null) { source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString)); @@ -835,5 +994,30 @@ public class Commands { source.sendMessage(Text.of(TextColor.GREEN, "Marker removed!")); return 1; } + + public int listMarkersCommand(CommandContext context) { + CommandSource source = commandSourceInterface.apply(context.getSource()); + + BlueMapAPI api = BlueMapAPI.getInstance().orElse(null); + if (api == null) { + source.sendMessage(Text.of(TextColor.RED, "MarkerAPI is not available, try ", TextColor.GRAY, "/bluemap reload")); + return 0; + } + + source.sendMessage(Text.of(TextColor.BLUE, "All Markers:")); + + int i = 0; + Collection markerIds = MarkerIdSuggestionProvider.getInstance().getPossibleValues(); + for (String markerId : markerIds) { + if (i++ >= 40) { + source.sendMessage(Text.of(TextColor.GRAY, "[" + (markerIds.size() - 40) + " more ...]")); + break; + } + + source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, markerId)); + } + + return 1; + } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/MapSuggestionProvider.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/MapSuggestionProvider.java index b683a7ab..f72f5d3c 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/MapSuggestionProvider.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/MapSuggestionProvider.java @@ -27,7 +27,7 @@ package de.bluecolored.bluemap.common.plugin.commands; import java.util.Collection; import java.util.HashSet; -import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.common.plugin.Plugin; public class MapSuggestionProvider extends AbstractSuggestionProvider { @@ -42,7 +42,7 @@ public class MapSuggestionProvider extends AbstractSuggestionProvider { public Collection getPossibleValues() { Collection values = new HashSet<>(); - for (MapType map : plugin.getMapTypes()) { + for (BmMap map : plugin.getMapTypes()) { values.add(map.getId()); } diff --git a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/TaskRefSuggestionProvider.java similarity index 72% rename from implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/TaskRefSuggestionProvider.java index 25b2ea9f..2e49ad01 100644 --- a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/TaskRefSuggestionProvider.java @@ -22,20 +22,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.fabric.events; +package de.bluecolored.bluemap.common.plugin.commands; -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.server.world.ServerWorld; +import java.util.Collection; -public interface WorldSaveCallback { - Event EVENT = EventFactory.createArrayBacked(WorldSaveCallback.class, - (listeners) -> (world) -> { - for (WorldSaveCallback event : listeners) { - event.onWorldSaved(world); - } - } - ); +public class TaskRefSuggestionProvider extends AbstractSuggestionProvider { - void onWorldSaved(ServerWorld world); + private CommandHelper helper; + + public TaskRefSuggestionProvider(CommandHelper helper) { + this.helper = helper; + } + + @Override + public Collection getPossibleValues() { + return helper.getTaskRefs(); + } + } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/WorldOrMapSuggestionProvider.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/WorldOrMapSuggestionProvider.java index 5bc100f1..4e32798d 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/WorldOrMapSuggestionProvider.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/WorldOrMapSuggestionProvider.java @@ -27,7 +27,7 @@ package de.bluecolored.bluemap.common.plugin.commands; import java.util.Collection; import java.util.HashSet; -import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.core.world.World; @@ -47,7 +47,7 @@ public class WorldOrMapSuggestionProvider extends AbstractSuggestionProvider< values.add(world.getName()); } - for (MapType map : plugin.getMapTypes()) { + for (BmMap map : plugin.getMapTypes()) { values.add(map.getId()); } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerEventListener.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerEventListener.java index 44dee9ba..c581b3b4 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerEventListener.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerEventListener.java @@ -24,22 +24,11 @@ */ package de.bluecolored.bluemap.common.plugin.serverinterface; -import java.util.UUID; - -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.common.plugin.text.Text; -public interface ServerEventListener { +import java.util.UUID; - default void onWorldSaveToDisk(UUID world) {}; - - default void onChunkSaveToDisk(UUID world, Vector2i chunkPos) {}; - - default void onBlockChange(UUID world, Vector3i blockPos) {}; - - default void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) {}; +public interface ServerEventListener { default void onPlayerJoin(UUID playerUuid) {}; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerInterface.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerInterface.java index 6e18abd0..5c1740a7 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerInterface.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/serverinterface/ServerInterface.java @@ -95,7 +95,7 @@ public interface ServerInterface { /** * Returns the state of the player with that UUID if present
- * this method is only guaranteed to return a {@link PlayerState} if the player is currently online. + * this method is only guaranteed to return a {@link Player} if the player is currently online. */ Optional getPlayer(UUID uuid); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkin.java index 836b3230..2a57af14 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkin.java @@ -27,6 +27,7 @@ package de.bluecolored.bluemap.common.plugin.skins; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonParser; +import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; import javax.imageio.ImageIO; @@ -41,6 +42,7 @@ import java.util.Base64; import java.util.UUID; import java.util.concurrent.*; +@DebugDump public class PlayerSkin { private final UUID uuid; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkinUpdater.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkinUpdater.java index 9e2d430d..81c55617 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkinUpdater.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkinUpdater.java @@ -25,6 +25,7 @@ package de.bluecolored.bluemap.common.plugin.skins; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.core.debug.DebugDump; import org.apache.commons.io.FileUtils; import java.io.File; @@ -33,6 +34,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +@DebugDump public class PlayerSkinUpdater implements ServerEventListener { private File storageFolder; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/CombinedRenderTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/CombinedRenderTask.java new file mode 100644 index 00000000..53eaf43c --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/CombinedRenderTask.java @@ -0,0 +1,112 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.rendermanager; + +import de.bluecolored.bluemap.core.debug.DebugDump; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +@DebugDump +public class CombinedRenderTask implements RenderTask { + + private final String description; + private final List tasks; + private int currentTaskIndex; + + public CombinedRenderTask(String description, Collection tasks) { + this.description = description; + this.tasks = Collections.unmodifiableList(new ArrayList<>(tasks)); + + this.currentTaskIndex = 0; + } + + @Override + public void doWork() throws Exception { + T task; + + synchronized (this) { + if (!hasMoreWork()) return; + task = this.tasks.get(this.currentTaskIndex); + + if (!task.hasMoreWork()){ + this.currentTaskIndex++; + return; + } + } + + task.doWork(); + } + + @Override + public synchronized boolean hasMoreWork() { + return this.currentTaskIndex < this.tasks.size(); + } + + @Override + public synchronized double estimateProgress() { + if (!hasMoreWork()) return 1; + + double total = currentTaskIndex; + total += this.tasks.get(this.currentTaskIndex).estimateProgress(); + + return total / tasks.size(); + } + + @Override + public void cancel() { + for (T task : tasks) task.cancel(); + } + + @Override + public boolean contains(RenderTask task) { + if (this.equals(task)) return true; + + if (task instanceof CombinedRenderTask) { + CombinedRenderTask combinedTask = (CombinedRenderTask) task; + + for (RenderTask subTask : combinedTask.tasks) { + if (!this.contains(subTask)) return false; + } + + return true; + } + + for (RenderTask subTask : this.tasks) { + if (subTask.contains(task)) return true; + } + + return false; + } + + @Override + public String getDescription() { + //return description + " (" + (this.currentTaskIndex + 1) + "/" + tasks.size() + ")"; + return description; + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java new file mode 100644 index 00000000..fd2a7a99 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java @@ -0,0 +1,121 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.rendermanager; + +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.util.FileUtils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.stream.Collectors; + +public class MapPurgeTask implements RenderTask { + + @DebugDump private final BmMap map; + @DebugDump private final Path directory; + @DebugDump private final int subFilesCount; + private final LinkedList subFiles; + + @DebugDump private volatile boolean hasMoreWork; + @DebugDump private volatile boolean cancelled; + + public MapPurgeTask(Path mapDirectory) throws IOException { + this(null, mapDirectory); + } + + public MapPurgeTask(BmMap map) throws IOException { + this(map, map.getFileRoot()); + } + + private MapPurgeTask(BmMap map, Path directory) throws IOException { + this.map = map; + this.directory = directory; + this.subFiles = Files.walk(directory, 3) + .collect(Collectors.toCollection(LinkedList::new)); + this.subFilesCount = subFiles.size(); + this.hasMoreWork = true; + this.cancelled = false; + } + + @Override + public void doWork() throws Exception { + synchronized (this) { + if (!this.hasMoreWork) return; + this.hasMoreWork = false; + } + + try { + // delete subFiles first to be able to track the progress and cancel + while (!subFiles.isEmpty()) { + Path subFile = subFiles.getLast(); + FileUtils.delete(subFile.toFile()); + subFiles.removeLast(); + if (this.cancelled) return; + } + + // make sure everything is deleted + FileUtils.delete(directory.toFile()); + } finally { + // reset map render state + if (this.map != null) { + this.map.getRenderState().reset(); + } + } + } + + @Override + public boolean hasMoreWork() { + return this.hasMoreWork; + } + + @Override + public double estimateProgress() { + return 1d - (subFiles.size() / (double) subFilesCount); + } + + @Override + public void cancel() { + this.cancelled = true; + } + + @Override + public boolean contains(RenderTask task) { + if (task == this) return true; + if (task instanceof MapPurgeTask) { + return ((MapPurgeTask) task).directory.toAbsolutePath().normalize().startsWith(this.directory.toAbsolutePath().normalize()); + } + + return false; + } + + @Override + public String getDescription() { + return "Purge Map " + directory.getFileName(); + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java new file mode 100644 index 00000000..c8f41c37 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java @@ -0,0 +1,116 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.rendermanager; + +import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.world.Grid; +import de.bluecolored.bluemap.core.world.World; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +@DebugDump +public class MapUpdateTask extends CombinedRenderTask { + + private final BmMap map; + private final Collection regions; + + public MapUpdateTask(BmMap map) { + this(map, getRegions(map.getWorld())); + } + + public MapUpdateTask(BmMap map, boolean force) { + this(map, getRegions(map.getWorld()), force); + } + + public MapUpdateTask(BmMap map, Vector2i center, int radius) { + this(map, getRegions(map.getWorld(), center, radius)); + } + + public MapUpdateTask(BmMap map, Vector2i center, int radius, boolean force) { + this(map, getRegions(map.getWorld(), center, radius), force); + } + + public MapUpdateTask(BmMap map, Collection regions) { + this(map, regions, false); + } + + public MapUpdateTask(BmMap map, Collection regions, boolean force) { + super("Update map '" + map.getId() + "'", createTasks(map, regions, force)); + this.map = map; + this.regions = Collections.unmodifiableCollection(new ArrayList<>(regions)); + } + + public BmMap getMap() { + return map; + } + + public Collection getRegions() { + return regions; + } + + private static Collection createTasks(BmMap map, Collection regions, boolean force) { + List tasks = new ArrayList<>(regions.size()); + regions.forEach(region -> tasks.add(new WorldRegionRenderTask(map, region, force))); + + // get spawn region + World world = map.getWorld(); + Vector2i spawnPoint = world.getSpawnPoint().toVector2(true); + Grid regionGrid = world.getRegionGrid(); + Vector2i spawnRegion = regionGrid.getCell(spawnPoint); + + tasks.sort(WorldRegionRenderTask.defaultComparator(spawnRegion)); + return tasks; + } + + private static List getRegions(World world) { + return getRegions(world, null, -1); + } + + private static List getRegions(World world, Vector2i center, int radius) { + if (center == null || radius < 0) return new ArrayList<>(world.listRegions()); + + List regions = new ArrayList<>(); + + Grid regionGrid = world.getRegionGrid(); + Vector2i halfCell = regionGrid.getGridSize().div(2); + int increasedRadiusSquared = (int) Math.pow(radius + Math.ceil(halfCell.length()), 2); + + for (Vector2i region : world.listRegions()) { + Vector2i min = regionGrid.getCellMin(region); + Vector2i regionCenter = min.add(halfCell); + + if (regionCenter.distanceSquared(center) <= increasedRadiusSquared) + regions.add(region); + } + + return regions; + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/ProgressTracker.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/ProgressTracker.java new file mode 100644 index 00000000..25e364e2 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/ProgressTracker.java @@ -0,0 +1,96 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.rendermanager; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class ProgressTracker { + private static final AtomicInteger ID = new AtomicInteger(0); + + private final Timer timer; + private Supplier progressSupplier; + private final int averagingCount; + + private long lastTime; + private double lastProgress; + + private final Deque timesPerProgress; + + public ProgressTracker(long updateIntervall, int averagingCount) { + this.timer = new Timer("BlueMap-ProgressTracker-Timer-" + ID.getAndIncrement(), true); + this.progressSupplier = () -> 0d; + this.averagingCount = averagingCount; + this.timesPerProgress = new LinkedList<>(); + + this.timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + update(); + } + }, updateIntervall, updateIntervall); + } + + public synchronized void resetAndStart(Supplier progressSupplier) { + this.progressSupplier = progressSupplier; + this.lastTime = System.currentTimeMillis(); + this.lastProgress = progressSupplier.get(); + this.timesPerProgress.clear(); + } + + public synchronized long getAverageTimePerProgress() { + return timesPerProgress.stream() + .collect(Collectors.averagingLong(Long::longValue)) + .longValue(); + } + + private synchronized void update() { + long now = System.currentTimeMillis(); + double progress = progressSupplier.get(); + + long deltaTime = now - lastTime; + double deltaProgress = progress - lastProgress; + + if (deltaProgress != 0) { + long totalDuration = (long) (deltaTime / deltaProgress); + + timesPerProgress.addLast(totalDuration); + while (timesPerProgress.size() > averagingCount) timesPerProgress.removeFirst(); + + this.lastTime = now; + this.lastProgress = progress; + } + } + + public void cancel() { + timer.cancel(); + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderManager.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderManager.java new file mode 100644 index 00000000..26a26ac7 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderManager.java @@ -0,0 +1,347 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.rendermanager; + +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.logger.Logger; + +import java.util.*; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +public class RenderManager { + private static final AtomicInteger nextRenderManagerIndex = new AtomicInteger(0); + + @DebugDump private final int id; + @DebugDump private volatile boolean running; + + private final AtomicInteger nextWorkerThreadIndex; + @DebugDump private final Collection workerThreads; + private final AtomicInteger busyCount; + + private ProgressTracker progressTracker; + private volatile boolean newTask; + + @DebugDump private final LinkedList renderTasks; + + public RenderManager() { + this.id = nextRenderManagerIndex.getAndIncrement(); + this.nextWorkerThreadIndex = new AtomicInteger(0); + + this.running = false; + this.workerThreads = new ConcurrentLinkedDeque<>(); + this.busyCount = new AtomicInteger(0); + + this.progressTracker = null; + this.newTask = true; + + this.renderTasks = new LinkedList<>(); + } + + public void start(int threadCount) throws IllegalStateException { + if (threadCount <= 0) throw new IllegalArgumentException("threadCount has to be 1 or more!"); + + synchronized (this.workerThreads) { + if (isRunning()) throw new IllegalStateException("RenderManager is already running!"); + this.workerThreads.clear(); + this.busyCount.set(0); + + if (progressTracker != null) progressTracker.cancel(); + progressTracker = new ProgressTracker(5000, 12); // 5-sec steps over one minute + this.newTask = true; + + this.running = true; + + for (int i = 0; i < threadCount; i++) { + WorkerThread worker = new WorkerThread(); + this.workerThreads.add(worker); + worker.start(); + } + } + } + + public void stop() { + synchronized (this.workerThreads) { + this.running = false; + for (WorkerThread worker : workerThreads) worker.interrupt(); + if (progressTracker != null) progressTracker.cancel(); + } + } + + public boolean isRunning() { + synchronized (this.workerThreads) { + for (WorkerThread worker : workerThreads) { + if (worker.isAlive()) return true; + } + + return false; + } + } + + public void awaitIdle() throws InterruptedException { + synchronized (this.renderTasks) { + while (!this.renderTasks.isEmpty()) + this.renderTasks.wait(10000); + } + } + + public void awaitShutdown() throws InterruptedException { + synchronized (this.workerThreads) { + while (isRunning()) + this.workerThreads.wait(10000); + } + } + + public boolean scheduleRenderTask(RenderTask task) { + synchronized (this.renderTasks) { + if (containsRenderTask(task)) return false; + + removeTasksThatAreContainedIn(task); + renderTasks.addLast(task); + renderTasks.notifyAll(); + return true; + } + } + + public int scheduleRenderTasks(RenderTask... tasks) { + return scheduleRenderTasks(Arrays.asList(tasks)); + } + + public int scheduleRenderTasks(Collection tasks) { + synchronized (this.renderTasks) { + int count = 0; + for (RenderTask task : tasks) { + if (scheduleRenderTask(task)) count++; + } + return count; + } + } + + public boolean scheduleRenderTaskNext(RenderTask task) { + synchronized (this.renderTasks) { + if (renderTasks.size() <= 1) return scheduleRenderTask(task); + if (containsRenderTask(task)) return false; + + removeTasksThatAreContainedIn(task); + renderTasks.add(1, task); + renderTasks.notifyAll(); + return true; + } + } + + public void reorderRenderTasks(Comparator taskComparator) { + synchronized (this.renderTasks) { + if (renderTasks.size() <= 2) return; + + RenderTask currentTask = renderTasks.removeFirst(); + renderTasks.sort(taskComparator); + renderTasks.addFirst(currentTask); + } + } + + public boolean removeRenderTask(RenderTask task) { + synchronized (this.renderTasks) { + if (this.renderTasks.isEmpty()) return false; + + // cancel the task if it is currently processed + RenderTask first = renderTasks.getFirst(); + if (first.equals(task)) { + first.cancel(); + return true; + } + + // else remove it + return renderTasks.remove(task); + } + } + + public void removeRenderTasksIf(Predicate removeCondition) { + synchronized (this.renderTasks) { + if (this.renderTasks.isEmpty()) return; + + RenderTask first = renderTasks.removeFirst(); + if (removeCondition.test(first)) first.cancel(); + renderTasks.removeIf(removeCondition); + renderTasks.addFirst(first); + } + } + + public void removeAllRenderTasks() { + synchronized (this.renderTasks) { + if (this.renderTasks.isEmpty()) return; + + RenderTask first = renderTasks.removeFirst(); + first.cancel(); + renderTasks.clear(); + renderTasks.addFirst(first); + } + } + + public long estimateCurrentRenderTaskTimeRemaining() { + if (progressTracker == null) return 0; + + synchronized (this.renderTasks) { + RenderTask task = getCurrentRenderTask(); + if (task == null) return 0; + + double progress = task.estimateProgress(); + long timePerProgress = progressTracker.getAverageTimePerProgress(); + return (long) ((1 - progress) * timePerProgress); + } + } + + public RenderTask getCurrentRenderTask() { + synchronized (this.renderTasks) { + if (this.renderTasks.isEmpty()) return null; + return this.renderTasks.getFirst(); + } + } + + public List getScheduledRenderTasks() { + synchronized (this.renderTasks) { + return new ArrayList<>(this.renderTasks); + } + } + + public int getScheduledRenderTaskCount() { + return this.renderTasks.size(); + } + + public boolean containsRenderTask(RenderTask task) { + synchronized (this.renderTasks) { + // checking all scheduled renderTasks except the first one, since that is already being processed + Iterator iterator = renderTasks.iterator(); + if (!iterator.hasNext()) return false; + iterator.next(); // skip first + + while(iterator.hasNext()) { + if (iterator.next().contains(task)) return true; + } + + return false; + } + } + + public int getWorkerThreadCount() { + return workerThreads.size(); + } + + private void removeTasksThatAreContainedIn(RenderTask containingTask) { + synchronized (this.renderTasks) { + if (renderTasks.size() < 2) return; + RenderTask first = renderTasks.removeFirst(); + renderTasks.removeIf(containingTask::contains); + renderTasks.addFirst(first); + } + } + + private void doWork() throws Exception { + RenderTask task; + + synchronized (this.renderTasks) { + while (this.renderTasks.isEmpty()) + this.renderTasks.wait(10000); + + task = this.renderTasks.getFirst(); + if (this.newTask) { + this.newTask = false; + this.progressTracker.resetAndStart(task::estimateProgress); + } + + // the following is making sure every render-thread is done working on this task (no thread is "busy") + // before continuing working on the next RenderTask + if (!task.hasMoreWork()) { + if (busyCount.get() <= 0) { + this.renderTasks.removeFirst(); + this.renderTasks.notifyAll(); + + this.newTask = true; + + busyCount.set(0); + } else { + this.renderTasks.wait(10000); + } + + return; + } + + this.busyCount.incrementAndGet(); + } + + try { + task.doWork(); + } finally { + synchronized (renderTasks) { + this.busyCount.decrementAndGet(); + this.renderTasks.notifyAll(); + } + } + } + + public class WorkerThread extends Thread { + + private final int id; + + private WorkerThread() { + this.id = RenderManager.this.nextWorkerThreadIndex.getAndIncrement(); + this.setName("RenderManager-" + RenderManager.this.id + "-" + this.id); + } + + @Override + @SuppressWarnings("BusyWait") + public void run() { + try { + while (RenderManager.this.running) { + try { + RenderManager.this.doWork(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + Logger.global.logError( + "RenderManager(" + RenderManager.this.id + "): WorkerThread(" + this.id + + "): Exception while doing some work!", e); + + try { + // on error, wait a few seconds before resurrecting this render-thread + // if something goes wrong, this prevents running into the same error on all render-threads + // with full-speed over and over again :D + Thread.sleep(10000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + } + } finally { + synchronized (RenderManager.this.workerThreads) { + RenderManager.this.workerThreads.remove(this); + RenderManager.this.workerThreads.notifyAll(); + } + } + } + + } + +} diff --git a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderTask.java similarity index 62% rename from implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderTask.java index d2647bfa..3d5f7ac9 100644 --- a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderTask.java @@ -22,23 +22,37 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.fabric.mixin; +package de.bluecolored.bluemap.common.rendermanager; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +public interface RenderTask { -import de.bluecolored.bluemap.fabric.events.WorldSaveCallback; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.ProgressListener; + void doWork() throws Exception; -@Mixin(ServerWorld.class) -public abstract class MixinServerWorld { + /** + * Whether this task is requesting more calls to its {@link #doWork()} method.
+ * This can be false because the task is finished, OR because the task got cancelled and decides to interrupt. + */ + boolean hasMoreWork(); - @Inject(at = @At("RETURN"), method = "save") - public void save(ProgressListener progressListener, boolean flush, boolean bl, CallbackInfo ci) { - WorldSaveCallback.EVENT.invoker().onWorldSaved((ServerWorld) (Object) this); + /** + * The estimated progress made so far, from 0 to 1. + */ + default double estimateProgress() { + return 0d; } - + + /** + * Requests to cancel this task. The task then self-decides what to do with this request. + */ + void cancel(); + + /** + * Checks if the given task is somehow included with this task + */ + default boolean contains(RenderTask task) { + return equals(task); + } + + String getDescription(); + } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java new file mode 100644 index 00000000..f910769e --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java @@ -0,0 +1,204 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.rendermanager; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector2l; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.world.Grid; +import de.bluecolored.bluemap.core.world.Region; + +import java.util.*; +import java.util.stream.Collectors; + +public class WorldRegionRenderTask implements RenderTask { + + @DebugDump private final BmMap map; + @DebugDump private final Vector2i worldRegion; + @DebugDump private final boolean force; + + private Deque tiles; + @DebugDump private int tileCount; + @DebugDump private long startTime; + + @DebugDump private volatile int atWork; + @DebugDump private volatile boolean cancelled; + + public WorldRegionRenderTask(BmMap map, Vector2i worldRegion) { + this(map, worldRegion, false); + } + + public WorldRegionRenderTask(BmMap map, Vector2i worldRegion, boolean force) { + this.map = map; + this.worldRegion = worldRegion; + this.force = force; + + this.tiles = null; + this.tileCount = -1; + this.startTime = -1; + + this.atWork = 0; + this.cancelled = false; + } + + private synchronized void init() { + Set tileSet = new HashSet<>(); + startTime = System.currentTimeMillis(); + + //Logger.global.logInfo("Starting: " + worldRegion); + + long changesSince = 0; + if (!force) changesSince = map.getRenderState().getRenderTime(worldRegion); + + Region region = map.getWorld().getRegion(worldRegion.getX(), worldRegion.getY()); + Collection chunks = region.listChunks(changesSince); + + Grid tileGrid = map.getHiresModelManager().getTileGrid(); + Grid chunkGrid = map.getWorld().getChunkGrid(); + + for (Vector2i chunk : chunks) { + Vector2i tileMin = chunkGrid.getCellMin(chunk, tileGrid); + Vector2i tileMax = chunkGrid.getCellMax(chunk, tileGrid); + + for (int x = tileMin.getX(); x <= tileMax.getX(); x++) { + for (int z = tileMin.getY(); z <= tileMax.getY(); z++) { + tileSet.add(new Vector2l(x, z)); + } + } + } + + this.tileCount = tileSet.size(); + this.tiles = tileSet.stream() + .sorted(WorldRegionRenderTask::compareVec2L) //sort with longs to avoid overflow (comparison uses distanceSquared) + .map(Vector2l::toInt) // back to ints + .collect(Collectors.toCollection(ArrayDeque::new)); + + if (tiles.isEmpty()) complete(); + } + + @Override + public void doWork() { + if (cancelled) return; + + Vector2i tile; + + synchronized (this) { + if (tiles == null) init(); + if (tiles.isEmpty()) return; + + tile = tiles.pollFirst(); + + this.atWork++; + } + + //Logger.global.logInfo("Working on " + worldRegion + " - Tile " + tile); + map.renderTile(tile); // <- actual work + + synchronized (this) { + this.atWork--; + + if (atWork <= 0 && tiles.isEmpty() && !cancelled) { + complete(); + } + } + } + + private void complete() { + map.getRenderState().setRenderTime(worldRegion, startTime); + + //Logger.global.logInfo("Done with: " + worldRegion); + } + + @Override + public synchronized boolean hasMoreWork() { + return !cancelled && (tiles == null || !tiles.isEmpty()); + } + + @Override + public double estimateProgress() { + if (tiles == null) return 0; + if (tileCount == 0) return 1; + + double remainingTiles = tiles.size(); + return 1 - (remainingTiles / this.tileCount); + } + + @Override + public void cancel() { + this.cancelled = true; + + synchronized (this) { + if (tiles != null) this.tiles.clear(); + } + } + + public BmMap getMap() { + return map; + } + + public Vector2i getWorldRegion() { + return worldRegion; + } + + public boolean isForce() { + return force; + } + + @Override + public String getDescription() { + return "Update region " + getWorldRegion() + " for map '" + map.getId() + "'"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WorldRegionRenderTask that = (WorldRegionRenderTask) o; + return force == that.force && map.getId().equals(that.map.getId()) && worldRegion.equals(that.worldRegion); + } + + @Override + public int hashCode() { + return worldRegion.hashCode(); + } + + public static Comparator defaultComparator(final Vector2i centerRegion) { + return (task1, task2) -> { + // use long to compare to avoid overflow (comparison uses distanceSquared) + Vector2l task1Rel = new Vector2l(task1.worldRegion.getX() - centerRegion.getX(), task1.worldRegion.getY() - centerRegion.getY()); + Vector2l task2Rel = new Vector2l(task2.worldRegion.getX() - centerRegion.getX(), task2.worldRegion.getY() - centerRegion.getY()); + return compareVec2L(task1Rel, task2Rel); + }; + } + + /** + * Comparison method that doesn't overflow that easily + */ + private static int compareVec2L(Vector2l v1, Vector2l v2) { + return Long.signum(v1.lengthSquared() - v2.lengthSquared()); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/FileRequestHandler.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/FileRequestHandler.java similarity index 99% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/FileRequestHandler.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/FileRequestHandler.java index f26f447c..a53b5c69 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/FileRequestHandler.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/FileRequestHandler.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.web; +package de.bluecolored.bluemap.common.web; import de.bluecolored.bluemap.core.webserver.HttpRequest; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/WebSettings.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/WebSettings.java new file mode 100644 index 00000000..bdfc4431 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/WebSettings.java @@ -0,0 +1,169 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.web; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3f; +import de.bluecolored.bluemap.core.config.MapConfig; +import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.util.FileUtils; +import de.bluecolored.bluemap.core.util.MathUtils; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.loader.ConfigurationLoader; +import org.spongepowered.configurate.serialize.SerializationException; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.stream.Collectors; + +public class WebSettings { + + private final ConfigurationLoader configLoader; + private ConfigurationNode rootNode; + + public WebSettings(File settingsFile) throws IOException { + FileUtils.createFile(settingsFile); + + configLoader = GsonConfigurationLoader.builder() + .file(settingsFile) + .build(); + + load(); + } + + public void load() throws IOException { + rootNode = configLoader.load(); + } + + public void save() throws IOException { + configLoader.save(rootNode); + } + + public void set(Object value, Object... path) throws SerializationException { + rootNode.node(path).set(value); + } + + public Object get(Object... path) { + return rootNode.node(path).raw(); + } + + public String getString(Object... path) { + return rootNode.node(path).getString(); + } + + public int getInt(Object... path) { + return rootNode.node(path).getInt(); + } + + public long getLong(Object... path) { + return rootNode.node(path).getLong(); + } + + public float getFloat(Object... path) { + return rootNode.node(path).getFloat(); + } + + public double getDouble(Object... path) { + return rootNode.node(path).getDouble(); + } + + public Collection getMapIds() { + return rootNode.node("maps").childrenMap().keySet().stream().map(Object::toString).collect(Collectors.toSet()); + } + + public void setAllMapsEnabled(boolean enabled) throws SerializationException { + for (ConfigurationNode mapNode : rootNode.node("maps").childrenMap().values()) { + mapNode.node("enabled").set(enabled); + } + } + + public void setMapEnabled(boolean enabled, String mapId) throws SerializationException { + set(enabled, "maps", mapId, "enabled"); + } + + public void setFrom(BmMap map) throws SerializationException { + Vector2i hiresTileSize = map.getHiresModelManager().getTileGrid().getGridSize(); + Vector2i gridOrigin = map.getHiresModelManager().getTileGrid().getOffset(); + Vector2i lowresTileSize = map.getLowresModelManager().getTileSize(); + Vector2i lowresPointsPerHiresTile = map.getLowresModelManager().getPointsPerHiresTile(); + + set(hiresTileSize.getX(), "maps", map.getId(), "hires", "tileSize", "x"); + set(hiresTileSize.getY(), "maps", map.getId(), "hires", "tileSize", "z"); + set(1, "maps", map.getId(), "hires", "scale", "x"); + set(1, "maps", map.getId(), "hires", "scale", "z"); + set(gridOrigin.getX(), "maps", map.getId(), "hires", "translate", "x"); + set(gridOrigin.getY(), "maps", map.getId(), "hires", "translate", "z"); + + Vector2i pointSize = hiresTileSize.div(lowresPointsPerHiresTile); + Vector2i tileSize = pointSize.mul(lowresTileSize); + + set(tileSize.getX(), "maps", map.getId(), "lowres", "tileSize", "x"); + set(tileSize.getY(), "maps", map.getId(), "lowres", "tileSize", "z"); + set(pointSize.getX(), "maps", map.getId(), "lowres", "scale", "x"); + set(pointSize.getY(), "maps", map.getId(), "lowres", "scale", "z"); + set(pointSize.getX() / 2, "maps", map.getId(), "lowres", "translate", "x"); + set(pointSize.getY() / 2, "maps", map.getId(), "lowres", "translate", "z"); + + set(map.getWorld().getSpawnPoint().getX(), "maps", map.getId(), "startPos", "x"); + set(map.getWorld().getSpawnPoint().getZ(), "maps", map.getId(), "startPos", "z"); + set(map.getWorld().getUUID().toString(), "maps", map.getId(), "world"); + } + + public void setFrom(MapConfig mapConfig) throws SerializationException { + Vector2i startPos = mapConfig.getStartPos(); + if (startPos != null) { + set(startPos.getX(), "maps", mapConfig.getId(), "startPos", "x"); + set(startPos.getY(), "maps", mapConfig.getId(), "startPos", "z"); + } + + Vector3f skyColor = MathUtils.color3FromInt(mapConfig.getSkyColor()); + set(skyColor.getX(), "maps", mapConfig.getId(), "skyColor", "r"); + set(skyColor.getY(), "maps", mapConfig.getId(), "skyColor", "g"); + set(skyColor.getZ(), "maps", mapConfig.getId(), "skyColor", "b"); + + set(mapConfig.getAmbientLight(), "maps", mapConfig.getId(), "ambientLight"); + + setName(mapConfig.getName(), mapConfig.getId()); + } + + public void setOrdinal(int ordinal, String mapId) throws SerializationException { + set(ordinal, "maps", mapId, "ordinal"); + } + + public int getOrdinal(String mapId) { + return getInt("maps", mapId, "ordinal"); + } + + public void setName(String name, String mapId) throws SerializationException { + set(name, "maps", mapId, "name"); + } + + public String getName(String mapId) { + return getString("maps", mapId, "name"); + } + +} diff --git a/BlueMapCore/build.gradle b/BlueMapCore/build.gradle index 18f50393..ebecdb25 100644 --- a/BlueMapCore/build.gradle +++ b/BlueMapCore/build.gradle @@ -1,23 +1,31 @@ dependencies { - compile 'com.github.ben-manes.caffeine:caffeine:2.8.5' - compile 'com.google.code.gson:gson:2.8.0' - compile 'org.apache.commons:commons-lang3:3.6' - compile group: 'commons-io', name: 'commons-io', version: '2.5' - compile 'com.flowpowered:flow-math:1.0.3' - compile 'org.spongepowered:configurate-hocon:3.7.1' - compile 'org.spongepowered:configurate-gson:3.7.1' - compile 'com.github.Querz:NBT:4.0' + api 'com.github.ben-manes.caffeine:caffeine:2.8.5' + api 'com.google.code.gson:gson:2.8.0' + api 'org.apache.commons:commons-lang3:3.6' + api group: 'commons-io', name: 'commons-io', version: '2.5' + api 'com.flowpowered:flow-math:1.0.3' + api 'org.spongepowered:configurate-hocon:4.1.1' + api 'org.spongepowered:configurate-gson:4.1.1' + api 'com.github.Querz:NBT:4.0' - testCompile 'junit:junit:4.12' + testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' +} + +test { + useJUnitPlatform() } processResources { + def git = versionDetails() from(sourceSets.main.resources.srcDirs) { include 'de/bluecolored/bluemap/version.json' - + duplicatesStrategy = DuplicatesStrategy.WARN + expand ( - version: project.version - ) + version: project.version, + gitHash: git.gitHashFull, + gitClean: git.isCleanTag + ) } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/BlueMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/BlueMap.java index e43c260c..20960eac 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/BlueMap.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/BlueMap.java @@ -24,26 +24,38 @@ */ package de.bluecolored.bluemap.core; +import de.bluecolored.bluemap.core.logger.Logger; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; + import java.io.IOException; import java.util.concurrent.ForkJoinPool; -import de.bluecolored.bluemap.core.logger.Logger; -import ninja.leaping.configurate.gson.GsonConfigurationLoader; - public class BlueMap { - public static final String VERSION; + public static final String VERSION, GIT_HASH, GIT_CLEAN; static { - String version = "DEV"; + String version = "DEV", gitHash = "DEV", gitClean = "DEV"; try { - version = GsonConfigurationLoader.builder().setURL(BlueMap.class.getResource("/de/bluecolored/bluemap/version.json")).build().load().getNode("version").getString("DEV"); + ConfigurationNode node = GsonConfigurationLoader.builder() + .url(BlueMap.class.getResource("/de/bluecolored/bluemap/version.json")) + .build() + .load(); + + version = node.node("version").getString("DEV"); + gitHash = node.node("git-hash").getString("DEV"); + gitClean = node.node("git-clean").getString("DEV"); } catch (IOException ex) { Logger.global.logError("Failed to load version.json from resources!", ex); } if (version.equals("${version}")) version = "DEV"; + if (gitHash.equals("${gitHash}")) version = "DEV"; + if (gitClean.equals("${gitClean}")) version = "DEV"; VERSION = version; + GIT_HASH = gitHash; + GIT_CLEAN = gitClean; } public static final ForkJoinPool THREAD_POOL = new ForkJoinPool(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/MinecraftVersion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/MinecraftVersion.java index 42138954..40fc7f7d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/MinecraftVersion.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/MinecraftVersion.java @@ -24,66 +24,155 @@ */ package de.bluecolored.bluemap.core; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.util.Lazy; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -public enum MinecraftVersion { - - MC_1_12 (101200, "1.12", "mc1_12", "https://launcher.mojang.com/v1/objects/0f275bc1547d01fa5f56ba34bdc87d981ee12daf/client.jar"), - MC_1_13 (101300, "1.13", "mc1_13", "https://launcher.mojang.com/v1/objects/30bfe37a8db404db11c7edf02cb5165817afb4d9/client.jar"), - MC_1_14 (101400, "1.14", "mc1_13", "https://launcher.mojang.com/v1/objects/8c325a0c5bd674dd747d6ebaa4c791fd363ad8a9/client.jar"), - MC_1_15 (101500, "1.15", "mc1_15", "https://launcher.mojang.com/v1/objects/e3f78cd16f9eb9a52307ed96ebec64241cc5b32d/client.jar"), - MC_1_16 (101600, "1.16", "mc1_16", "https://launcher.mojang.com/v1/objects/653e97a2d1d76f87653f02242d243cdee48a5144/client.jar"); +public class MinecraftVersion implements Comparable { private static final Pattern VERSION_REGEX = Pattern.compile("(?:(?\\d+)\\.(?\\d+))(?:\\.(?\\d+))?(?:\\-(?:pre|rc)\\d+)?"); - private final int versionOrdinal; - private final String versionString; - private final String resourcePrefix; - private final String clientDownloadUrl; - - MinecraftVersion(int versionOrdinal, String versionString, String resourcePrefix, String clientDownloadUrl) { - this.versionOrdinal = versionOrdinal; - this.versionString = versionString; - this.resourcePrefix = resourcePrefix; - this.clientDownloadUrl = clientDownloadUrl; + public static final MinecraftVersion LATEST_SUPPORTED = new MinecraftVersion(1, 17, 0); + public static final MinecraftVersion EARLIEST_SUPPORTED = new MinecraftVersion(1, 12, 2); + public static final MinecraftVersion THE_FLATTENING = new MinecraftVersion(1, 13); + + @DebugDump + private final int major, minor, patch; + + @DebugDump + private final Lazy resource; + + public MinecraftVersion(int major, int minor) { + this(major, minor, 0); + } + + public MinecraftVersion(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + + this.resource = new Lazy<>(this::findBestMatchingResource); } public String getVersionString() { - return this.versionString; + return major + "." + minor + "." + patch; } - public String getResourcePrefix() { - return this.resourcePrefix; - } - - public String getClientDownloadUrl() { - return this.clientDownloadUrl; + public MinecraftResource getResource() { + return this.resource.getValue(); } public boolean isAtLeast(MinecraftVersion minVersion) { - return this.versionOrdinal >= minVersion.versionOrdinal; + return compareTo(minVersion) >= 0; } public boolean isAtMost(MinecraftVersion maxVersion) { - return this.versionOrdinal <= maxVersion.versionOrdinal; + return compareTo(maxVersion) <= 0; } - public static MinecraftVersion fromVersionString(String versionString) { + public boolean isBefore(MinecraftVersion minVersion) { + return compareTo(minVersion) < 0; + } + + public boolean isAfter(MinecraftVersion minVersion) { + return compareTo(minVersion) > 0; + } + + @Override + public int compareTo(MinecraftVersion other) { + int result; + + result = Integer.compare(major, other.major); + if (result != 0) return result; + + result = Integer.compare(minor, other.minor); + if (result != 0) return result; + + result = Integer.compare(patch, other.patch); + return result; + } + + public boolean majorEquals(MinecraftVersion that) { + return major == that.major; + } + + public boolean minorEquals(MinecraftVersion that) { + return major == that.major && minor == that.minor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MinecraftVersion that = (MinecraftVersion) o; + return major == that.major && minor == that.minor && patch == that.patch; + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch); + } + + private MinecraftResource findBestMatchingResource() { + MinecraftResource[] resources = MinecraftResource.values(); + Arrays.sort(resources, Comparator.comparing(MinecraftResource::getVersion).reversed()); + + for (MinecraftResource resource : resources){ + if (isAtLeast(resource.version)) return resource; + } + + return resources[resources.length - 1]; + } + + public static MinecraftVersion of(String versionString) { Matcher matcher = VERSION_REGEX.matcher(versionString); if (!matcher.matches()) throw new IllegalArgumentException("Not a valid version string!"); - - String normalizedVersionString = matcher.group("major") + "." + matcher.group("minor"); - - for (MinecraftVersion mcv : values()) { - if (mcv.versionString.equals(normalizedVersionString)) return mcv; - } - - throw new IllegalArgumentException("No matching version found!"); + + int major = Integer.parseInt(matcher.group("major")); + int minor = Integer.parseInt(matcher.group("minor")); + int patch = 0; + String patchString = matcher.group("patch"); + if (patchString != null) patch = Integer.parseInt(patchString); + + return new MinecraftVersion(major, minor, patch); } - public static MinecraftVersion getLatest() { - return MC_1_16; + public enum MinecraftResource { + + MC_1_12 (new MinecraftVersion(1, 12), "mc1_12", "https://launcher.mojang.com/v1/objects/0f275bc1547d01fa5f56ba34bdc87d981ee12daf/client.jar"), + MC_1_13 (new MinecraftVersion(1, 13), "mc1_13", "https://launcher.mojang.com/v1/objects/30bfe37a8db404db11c7edf02cb5165817afb4d9/client.jar"), + MC_1_14 (new MinecraftVersion(1, 14), "mc1_13", "https://launcher.mojang.com/v1/objects/8c325a0c5bd674dd747d6ebaa4c791fd363ad8a9/client.jar"), + MC_1_15 (new MinecraftVersion(1, 15), "mc1_15", "https://launcher.mojang.com/v1/objects/e3f78cd16f9eb9a52307ed96ebec64241cc5b32d/client.jar"), + MC_1_16 (new MinecraftVersion(1, 16), "mc1_16", "https://launcher.mojang.com/v1/objects/228fdf45541c4c2fe8aec4f20e880cb8fcd46621/client.jar"), + MC_1_16_2 (new MinecraftVersion(1, 16, 2), "mc1_16", "https://launcher.mojang.com/v1/objects/653e97a2d1d76f87653f02242d243cdee48a5144/client.jar"), + MC_1_17 (new MinecraftVersion(1, 17), "mc1_16", "https://launcher.mojang.com/v1/objects/1cf89c77ed5e72401b869f66410934804f3d6f52/client.jar"); + + @DebugDump private final MinecraftVersion version; + @DebugDump private final String resourcePrefix; + @DebugDump private final String clientUrl; + + MinecraftResource(MinecraftVersion version, String resourcePrefix, String clientUrl) { + this.version = version; + this.resourcePrefix = resourcePrefix; + this.clientUrl = clientUrl; + } + + public MinecraftVersion getVersion() { + return version; + } + + public String getResourcePrefix() { + return resourcePrefix; + } + + public String getClientUrl() { + return clientUrl; + } } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BiomeConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BiomeConfig.java index 51ce91fb..a74e7f27 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BiomeConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BiomeConfig.java @@ -24,21 +24,21 @@ */ package de.bluecolored.bluemap.core.config; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; +import de.bluecolored.bluemap.core.world.Biome; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.loader.ConfigurationLoader; + import java.io.IOException; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; -import de.bluecolored.bluemap.core.world.Biome; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.loader.ConfigurationLoader; - public class BiomeConfig implements BiomeMapper { - private ConfigurationLoader autopoulationConfigLoader; - private Map biomes; + private final ConfigurationLoader autopoulationConfigLoader; + private final Map biomes; public BiomeConfig(ConfigurationNode node) { this(node, null); @@ -49,7 +49,7 @@ public class BiomeConfig implements BiomeMapper { biomes = new ConcurrentHashMap<>(200, 0.5f, 8); - for (Entry e : node.getChildrenMap().entrySet()){ + for (Entry e : node.childrenMap().entrySet()){ String id = e.getKey().toString(); Biome biome = Biome.create(id, e.getValue()); biomes.put(biome.getNumeralId(), biome); @@ -68,7 +68,7 @@ public class BiomeConfig implements BiomeMapper { synchronized (autopoulationConfigLoader) { try { ConfigurationNode node = autopoulationConfigLoader.load(); - node.getNode("unknown:" + id).getNode("id").setValue(id); + node.node("unknown:" + id).node("id").set(id); autopoulationConfigLoader.save(node); } catch (IOException ex) { Logger.global.noFloodError("biomeconf-autopopulate-ioex", "Failed to auto-populate BiomeConfig!", ex); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockIdConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockIdConfig.java index 104db897..5535c911 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockIdConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockIdConfig.java @@ -24,12 +24,11 @@ */ package de.bluecolored.bluemap.core.config; -import com.google.common.base.Preconditions; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper; import de.bluecolored.bluemap.core.world.BlockState; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.loader.ConfigurationLoader; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.loader.ConfigurationLoader; import java.io.IOException; import java.util.Map; @@ -57,7 +56,7 @@ public class BlockIdConfig implements BlockIdMapper { this.lock = new ReentrantReadWriteLock(); - for (Entry e : node.getChildrenMap().entrySet()){ + for (Entry e : node.childrenMap().entrySet()){ String key = e.getKey().toString(); String value = e.getValue().getString(); @@ -119,7 +118,7 @@ public class BlockIdConfig implements BlockIdMapper { if (autopoulationConfigLoader != null) { try { ConfigurationNode node = autopoulationConfigLoader.load(); - node.getNode(numeralId + ":" + meta).setValue(state.toString()); + node.node(numeralId + ":" + meta).set(state.toString()); autopoulationConfigLoader.save(node); } catch (IOException ex) { Logger.global.noFloodError("blockidconf-autopopulate-ioex", "Failed to auto-populate BlockIdConfig!", ex); @@ -165,12 +164,11 @@ public class BlockIdConfig implements BlockIdMapper { } idMappings.put(idmeta, state); - Preconditions.checkArgument(numeralMappings.put(numidmeta, state) == null); if (autopoulationConfigLoader != null) { try { ConfigurationNode node = autopoulationConfigLoader.load(); - node.getNode(id + ":" + meta).setValue(state.toString()); + node.node(id + ":" + meta).set(state.toString()); autopoulationConfigLoader.save(node); } catch (IOException ex) { Logger.global.noFloodError("blockidconf-autopopulate-ioex", "Failed to auto-populate BlockIdConfig!", ex); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockPropertiesConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockPropertiesConfig.java index 3e67126b..1a72e9c7 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockPropertiesConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockPropertiesConfig.java @@ -24,15 +24,8 @@ */ package de.bluecolored.bluemap.core.config; -import java.io.IOException; -import java.util.Map.Entry; - import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; -import com.google.common.collect.Multimap; -import com.google.common.collect.MultimapBuilder; -import com.google.common.collect.Multimaps; - import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper; @@ -41,39 +34,47 @@ import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.resourcepack.TransformedBlockModelResource; import de.bluecolored.bluemap.core.world.BlockProperties; import de.bluecolored.bluemap.core.world.BlockState; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.loader.ConfigurationLoader; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.loader.ConfigurationLoader; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; public class BlockPropertiesConfig implements BlockPropertiesMapper { - private ConfigurationLoader autopoulationConfigLoader; + private final ConfigurationLoader autopoulationConfigLoader; - private Multimap> mappings; - private LoadingCache mappingCache; + private final Map>> mappings; + private final LoadingCache mappingCache; - private ResourcePack resourcePack = null; + private final ResourcePack resourcePack; - public BlockPropertiesConfig(ConfigurationNode node, ResourcePack resourcePack) throws IOException { + public BlockPropertiesConfig(ConfigurationNode node, ResourcePack resourcePack) { this(node, resourcePack, null); } - public BlockPropertiesConfig(ConfigurationNode node, ResourcePack resourcePack, ConfigurationLoader autopoulationConfigLoader) throws IOException { + public BlockPropertiesConfig(ConfigurationNode node, ResourcePack resourcePack, ConfigurationLoader autopoulationConfigLoader) { this.resourcePack = resourcePack; this.autopoulationConfigLoader = autopoulationConfigLoader; + + mappings = new ConcurrentHashMap<>(); - mappings = Multimaps.synchronizedListMultimap(MultimapBuilder.hashKeys().arrayListValues().build()); - - for (Entry e : node.getChildrenMap().entrySet()){ + for (Entry e : node.childrenMap().entrySet()){ String key = e.getKey().toString(); try { BlockState bsKey = BlockState.fromString(key); BlockProperties bsValue = new BlockProperties( - e.getValue().getNode("culling").getBoolean(true), - e.getValue().getNode("occluding").getBoolean(true), - e.getValue().getNode("flammable").getBoolean(false) + e.getValue().node("culling").getBoolean(true), + e.getValue().node("occluding").getBoolean(true), + e.getValue().node("flammable").getBoolean(false) ); BlockStateMapping mapping = new BlockStateMapping<>(bsKey, bsValue); - mappings.put(bsKey.getFullId(), mapping); + mappings.computeIfAbsent(bsKey.getFullId(), k -> new ArrayList<>()).add(mapping); } catch (IllegalArgumentException ex) { Logger.global.logWarning("Loading BlockPropertiesConfig: Failed to parse BlockState from key '" + key + "'"); } @@ -82,7 +83,7 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper { mappingCache = Caffeine.newBuilder() .executor(BlueMap.THREAD_POOL) .maximumSize(10000) - .build(key -> mapNoCache(key)); + .build(this::mapNoCache); } @Override @@ -91,7 +92,7 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper { } private BlockProperties mapNoCache(BlockState bs){ - for (BlockStateMapping bm : mappings.get(bs.getFullId())){ + for (BlockStateMapping bm : mappings.getOrDefault(bs.getFullId(), Collections.emptyList())){ if (bm.fitsTo(bs)){ return bm.getMapping(); } @@ -114,15 +115,15 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper { } catch (NoSuchResourceException ignore) {} //ignoring this because it will be logged later again if we try to render that block } - mappings.put(bs.getFullId(), new BlockStateMapping(new BlockState(bs.getFullId()), generated)); + mappings.computeIfAbsent(bs.getFullId(), k -> new ArrayList<>()).add(new BlockStateMapping<>(new BlockState(bs.getFullId()), generated)); if (autopoulationConfigLoader != null) { synchronized (autopoulationConfigLoader) { try { ConfigurationNode node = autopoulationConfigLoader.load(); - ConfigurationNode bpNode = node.getNode(bs.getFullId()); - bpNode.getNode("culling").setValue(generated.isCulling()); - bpNode.getNode("occluding").setValue(generated.isOccluding()); - bpNode.getNode("flammable").setValue(generated.isFlammable()); + ConfigurationNode bpNode = node.node(bs.getFullId()); + bpNode.node("culling").set(generated.isCulling()); + bpNode.node("occluding").set(generated.isOccluding()); + bpNode.node("flammable").set(generated.isFlammable()); autopoulationConfigLoader.save(node); } catch (IOException ex) { Logger.global.noFloodError("blockpropsconf-autopopulate-ioex", "Failed to auto-populate BlockPropertiesConfig!", ex); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java index 14ed8fe9..79f4883d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java @@ -24,16 +24,16 @@ */ package de.bluecolored.bluemap.core.config; -import com.google.common.base.Preconditions; + import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.resourcepack.ResourcePack.Resource; import de.bluecolored.bluemap.core.util.FileUtils; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.gson.GsonConfigurationLoader; -import ninja.leaping.configurate.hocon.HoconConfigurationLoader; -import ninja.leaping.configurate.loader.ConfigurationLoader; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.hocon.HoconConfigurationLoader; +import org.spongepowered.configurate.loader.ConfigurationLoader; import java.io.*; import java.net.URL; @@ -41,6 +41,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.time.LocalDateTime; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -96,7 +97,7 @@ public class ConfigManager { } else { //create empty config ConfigurationLoader loader = getLoader(configFile); - configNode = loader.createEmptyNode(); + configNode = loader.createNode(); //save to create file if (generateEmptyConfig) loader.save(configNode); @@ -109,7 +110,7 @@ public class ConfigManager { //populate missing values with default values if (defaultValues != null) { ConfigurationNode defaultValuesNode = getLoader(defaultValues).load(); - configNode.mergeValuesFrom(defaultValuesNode); + configNode.mergeFrom(defaultValuesNode); } return configNode; @@ -118,7 +119,7 @@ public class ConfigManager { public void loadResourceConfigs(File configFolder, ResourcePack resourcePack) throws IOException { //load blockColors.json from resources, config-folder and resourcepack - URL blockColorsConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResourcePrefix() + "/blockColors.json"); + URL blockColorsConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResource().getResourcePrefix() + "/blockColors.json"); File blockColorsConfigFile = new File(configFolder, "blockColors.json"); ConfigurationNode blockColorsConfigNode = loadOrCreate( blockColorsConfigFile, @@ -131,7 +132,7 @@ public class ConfigManager { resourcePack.getBlockColorCalculator().loadColorConfig(blockColorsConfigNode); //load blockIds.json from resources, config-folder and resourcepack - URL blockIdsConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResourcePrefix() + "/blockIds.json"); + URL blockIdsConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResource().getResourcePrefix() + "/blockIds.json"); File blockIdsConfigFile = new File(configFolder, "blockIds.json"); ConfigurationNode blockIdsConfigNode = loadOrCreate( blockIdsConfigFile, @@ -146,7 +147,7 @@ public class ConfigManager { ); //load blockProperties.json from resources, config-folder and resourcepack - URL blockPropertiesConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResourcePrefix() + "/blockProperties.json"); + URL blockPropertiesConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResource().getResourcePrefix() + "/blockProperties.json"); File blockPropertiesConfigFile = new File(configFolder, "blockProperties.json"); ConfigurationNode blockPropertiesConfigNode = loadOrCreate( blockPropertiesConfigFile, @@ -162,7 +163,7 @@ public class ConfigManager { ); //load biomes.json from resources, config-folder and resourcepack - URL biomeConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResourcePrefix() + "/biomes.json"); + URL biomeConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResource().getResourcePrefix() + "/biomes.json"); File biomeConfigFile = new File(configFolder, "biomes.json"); ConfigurationNode biomeConfigNode = loadOrCreate( biomeConfigFile, @@ -196,7 +197,7 @@ public class ConfigManager { try { ConfigurationNode node = getLoader(configFileName, resource.read()).load(); if (joinedNode == null) joinedNode = node; - else joinedNode.mergeValuesFrom(node); + else joinedNode.mergeFrom(node); } catch (IOException ex) { Logger.global.logWarning("Failed to load an additional " + configFileName + " from the resource-pack! " + ex); } @@ -204,7 +205,7 @@ public class ConfigManager { if (joinedNode == null) return defaultConfig; - joinedNode.mergeValuesFrom(defaultConfig); + joinedNode.mergeFrom(defaultConfig); return joinedNode; } @@ -212,22 +213,22 @@ public class ConfigManager { private ConfigurationLoader getLoader(String filename, InputStream is){ BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); - if (filename.endsWith(".json")) return GsonConfigurationLoader.builder().setSource(() -> reader).build(); - else return HoconConfigurationLoader.builder().setSource(() -> reader).build(); + if (filename.endsWith(".json")) return GsonConfigurationLoader.builder().source(() -> reader).build(); + else return HoconConfigurationLoader.builder().source(() -> reader).build(); } private ConfigurationLoader getLoader(URL url){ - if (url.getFile().endsWith(".json")) return GsonConfigurationLoader.builder().setURL(url).build(); - else return HoconConfigurationLoader.builder().setURL(url).build(); + if (url.getFile().endsWith(".json")) return GsonConfigurationLoader.builder().url(url).build(); + else return HoconConfigurationLoader.builder().url(url).build(); } private ConfigurationLoader getLoader(File file){ - if (file.getName().endsWith(".json")) return GsonConfigurationLoader.builder().setFile(file).build(); - else return HoconConfigurationLoader.builder().setFile(file).build(); + if (file.getName().endsWith(".json")) return GsonConfigurationLoader.builder().file(file).build(); + else return HoconConfigurationLoader.builder().file(file).build(); } public static File toFolder(String pathString) throws IOException { - Preconditions.checkNotNull(pathString); + Objects.requireNonNull(pathString); File file = new File(pathString); if (file.exists() && !file.isDirectory()) throw new IOException("Invalid configuration: Path '" + file.getAbsolutePath() + "' is a file (should be a directory)"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/CoreConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/CoreConfig.java index 80b4221a..e57c84ec 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/CoreConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/CoreConfig.java @@ -24,35 +24,36 @@ */ package de.bluecolored.bluemap.core.config; -import ninja.leaping.configurate.ConfigurationNode; +import de.bluecolored.bluemap.core.debug.DebugDump; +import org.spongepowered.configurate.ConfigurationNode; import java.io.File; import java.io.IOException; public class CoreConfig { - private boolean downloadAccepted = false; - private int renderThreadCount = 0; - private boolean metricsEnabled = false; - private File dataFolder = new File("data"); + @DebugDump private boolean downloadAccepted = false; + @DebugDump private int renderThreadCount = 0; + @DebugDump private boolean metricsEnabled = false; + @DebugDump private File dataFolder = new File("data"); public CoreConfig(ConfigurationNode node) throws IOException { //accept-download - downloadAccepted = node.getNode("accept-download").getBoolean(false); + downloadAccepted = node.node("accept-download").getBoolean(false); //renderThreadCount int processors = Runtime.getRuntime().availableProcessors(); - renderThreadCount = node.getNode("renderThreadCount").getInt(0); + renderThreadCount = node.node("renderThreadCount").getInt(0); if (renderThreadCount <= 0) renderThreadCount = processors + renderThreadCount; if (renderThreadCount <= 0) renderThreadCount = 1; //metrics - metricsEnabled = node.getNode("metrics").getBoolean(false); + metricsEnabled = node.node("metrics").getBoolean(false); //data - dataFolder = ConfigManager.toFolder(node.getNode("data").getString("data")); + dataFolder = ConfigManager.toFolder(node.node("data").getString("data")); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java index f1f20be1..2c27e756 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java @@ -24,17 +24,18 @@ */ package de.bluecolored.bluemap.core.config; +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.map.MapSettings; +import de.bluecolored.bluemap.core.util.ConfigUtils; +import org.spongepowered.configurate.ConfigurationNode; + import java.io.IOException; import java.util.regex.Pattern; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - -import de.bluecolored.bluemap.core.render.RenderSettings; -import de.bluecolored.bluemap.core.util.ConfigUtils; -import ninja.leaping.configurate.ConfigurationNode; - -public class MapConfig implements RenderSettings { +@DebugDump +public class MapConfig implements MapSettings { private static final Pattern VALID_ID_PATTERN = Pattern.compile("[a-zA-Z0-9_]+"); private String id; @@ -61,53 +62,53 @@ public class MapConfig implements RenderSettings { public MapConfig(ConfigurationNode node) throws IOException { //id - this.id = node.getNode("id").getString(""); + this.id = node.node("id").getString(""); if (id.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].id is not defined"); if (!VALID_ID_PATTERN.matcher(id).matches()) throw new IOException("Invalid configuration: Node maps[?].id '" + id + "' has invalid characters in it"); //name - this.name = node.getNode("name").getString(id); + this.name = node.node("name").getString(id); //world - this.world = node.getNode("world").getString(""); + this.world = node.node("world").getString(""); if (world.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].world is not defined"); //startPos - if (!node.getNode("startPos").isVirtual()) this.startPos = ConfigUtils.readVector2i(node.getNode("startPos")); + if (!node.node("startPos").virtual()) this.startPos = ConfigUtils.readVector2i(node.node("startPos")); //skyColor - if (!node.getNode("skyColor").isVirtual()) this.skyColor = ConfigUtils.readColorInt(node.getNode("skyColor")); + if (!node.node("skyColor").virtual()) this.skyColor = ConfigUtils.readColorInt(node.node("skyColor")); else this.skyColor = 0x7dabff; //ambientLight - this.ambientLight = node.getNode("ambientLight").getFloat(0f); + this.ambientLight = node.node("ambientLight").getFloat(0f); //renderCaves - this.renderCaves = node.getNode("renderCaves").getBoolean(false); + this.renderCaves = node.node("renderCaves").getBoolean(false); //bounds - int minX = node.getNode("minX").getInt(RenderSettings.super.getMin().getX()); - int maxX = node.getNode("maxX").getInt(RenderSettings.super.getMax().getX()); - int minZ = node.getNode("minZ").getInt(RenderSettings.super.getMin().getZ()); - int maxZ = node.getNode("maxZ").getInt(RenderSettings.super.getMax().getZ()); - int minY = node.getNode("minY").getInt(RenderSettings.super.getMin().getY()); - int maxY = node.getNode("maxY").getInt(RenderSettings.super.getMax().getY()); + int minX = node.node("minX").getInt(MapSettings.super.getMin().getX()); + int maxX = node.node("maxX").getInt(MapSettings.super.getMax().getX()); + int minZ = node.node("minZ").getInt(MapSettings.super.getMin().getZ()); + int maxZ = node.node("maxZ").getInt(MapSettings.super.getMax().getZ()); + int minY = node.node("minY").getInt(MapSettings.super.getMin().getY()); + int maxY = node.node("maxY").getInt(MapSettings.super.getMax().getY()); this.min = new Vector3i(minX, minY, minZ); this.max = new Vector3i(maxX, maxY, maxZ); //renderEdges - this.renderEdges = node.getNode("renderEdges").getBoolean(true); + this.renderEdges = node.node("renderEdges").getBoolean(true); //useCompression - this.useGzip = node.getNode("useCompression").getBoolean(true); + this.useGzip = node.node("useCompression").getBoolean(true); //ignoreMissingLightData - this.ignoreMissingLightData = node.getNode("ignoreMissingLightData").getBoolean(false); + this.ignoreMissingLightData = node.node("ignoreMissingLightData").getBoolean(false); //tile-settings - this.hiresTileSize = node.getNode("hires", "tileSize").getInt(32); - this.lowresPointsPerHiresTile = node.getNode("lowres", "pointsPerHiresTile").getInt(4); - this.lowresPointsPerLowresTile = node.getNode("lowres", "pointsPerLowresTile").getInt(50); + this.hiresTileSize = node.node("hires", "tileSize").getInt(32); + this.lowresPointsPerHiresTile = node.node("lowres", "pointsPerHiresTile").getInt(4); + this.lowresPointsPerLowresTile = node.node("lowres", "pointsPerLowresTile").getInt(50); //check valid tile configuration values double blocksPerPoint = (double) this.hiresTileSize / (double) this.lowresPointsPerHiresTile; @@ -146,15 +147,18 @@ public class MapConfig implements RenderSettings { public boolean isIgnoreMissingLightData() { return ignoreMissingLightData; } - + + @Override public int getHiresTileSize() { return hiresTileSize; } + @Override public int getLowresPointsPerHiresTile() { return lowresPointsPerHiresTile; } + @Override public int getLowresPointsPerLowresTile() { return lowresPointsPerLowresTile; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java index 42056ef9..800b300a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java @@ -24,32 +24,38 @@ */ package de.bluecolored.bluemap.core.config; -import ninja.leaping.configurate.ConfigurationNode; +import de.bluecolored.bluemap.core.debug.DebugDump; +import org.spongepowered.configurate.ConfigurationNode; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +@DebugDump public class RenderConfig { private File webRoot = new File("web"); private boolean useCookies; + private boolean enableFreeFlight; private List mapConfigs = new ArrayList<>(); public RenderConfig(ConfigurationNode node) throws IOException { //webroot - String webRootString = node.getNode("webroot").getString(); + String webRootString = node.node("webroot").getString(); if (webRootString == null) throw new IOException("Invalid configuration: Node webroot is not defined"); webRoot = ConfigManager.toFolder(webRootString); //cookies - useCookies = node.getNode("useCookies").getBoolean(true); + useCookies = node.node("useCookies").getBoolean(true); + + // free-flight mode + enableFreeFlight = node.node("enableFreeFlight").getBoolean(true); //maps mapConfigs = new ArrayList<>(); - for (ConfigurationNode mapConfigNode : node.getNode("maps").getChildrenList()) { + for (ConfigurationNode mapConfigNode : node.node("maps").childrenList()) { mapConfigs.add(new MapConfig(mapConfigNode)); } @@ -62,7 +68,11 @@ public class RenderConfig { public boolean isUseCookies() { return useCookies; } - + + public boolean isEnableFreeFlight() { + return enableFreeFlight; + } + public List getMapConfigs(){ return mapConfigs; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/WebServerConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/WebServerConfig.java index 2f0af269..e465301e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/WebServerConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/WebServerConfig.java @@ -24,13 +24,15 @@ */ package de.bluecolored.bluemap.core.config; -import ninja.leaping.configurate.ConfigurationNode; +import de.bluecolored.bluemap.core.debug.DebugDump; +import org.spongepowered.configurate.ConfigurationNode; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; +@DebugDump public class WebServerConfig { private boolean enabled = true; @@ -43,16 +45,16 @@ public class WebServerConfig { public WebServerConfig(ConfigurationNode node) throws IOException { //enabled - enabled = node.getNode("enabled").getBoolean(false); + enabled = node.node("enabled").getBoolean(false); if (enabled) { //webroot - String webRootString = node.getNode("webroot").getString(); + String webRootString = node.node("webroot").getString(); if (webRootString == null) throw new IOException("Invalid configuration: Node webroot is not defined"); webRoot = ConfigManager.toFolder(webRootString); //ip - String bindAddressString = node.getNode("ip").getString(""); + String bindAddressString = node.node("ip").getString(""); if (bindAddressString.isEmpty() || bindAddressString.equals("0.0.0.0") || bindAddressString.equals("::0")) { bindAddress = new InetSocketAddress(0).getAddress(); // 0.0.0.0 } else if (bindAddressString.equals("#getLocalHost")) { @@ -62,10 +64,10 @@ public class WebServerConfig { } //port - port = node.getNode("port").getInt(8100); + port = node.node("port").getInt(8100); //maxConnectionCount - maxConnections = node.getNode("maxConnectionCount").getInt(100); + maxConnections = node.node("maxConnectionCount").getInt(100); } } diff --git a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/DebugDump.java similarity index 72% rename from implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/DebugDump.java index 25b2ea9f..ffb63191 100644 --- a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/DebugDump.java @@ -22,20 +22,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.fabric.events; +package de.bluecolored.bluemap.core.debug; -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.server.world.ServerWorld; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -public interface WorldSaveCallback { - Event EVENT = EventFactory.createArrayBacked(WorldSaveCallback.class, - (listeners) -> (world) -> { - for (WorldSaveCallback event : listeners) { - event.onWorldSaved(world); - } - } - ); +@Retention(RetentionPolicy.RUNTIME) +@Target({ + ElementType.METHOD, + ElementType.FIELD, + ElementType.TYPE +}) +public @interface DebugDump { + + String value() default ""; - void onWorldSaved(ServerWorld world); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/StateDumper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/StateDumper.java new file mode 100644 index 00000000..282b50fb --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/StateDumper.java @@ -0,0 +1,236 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.debug; + +import de.bluecolored.bluemap.core.BlueMap; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.serialize.SerializationException; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.*; + +public class StateDumper { + + private static final StateDumper GLOBAL = new StateDumper(); + + private final Set instances = Collections.newSetFromMap(new WeakHashMap<>()); + + public void dump(Path file) throws IOException { + GsonConfigurationLoader loader = GsonConfigurationLoader.builder() + .path(file) + .build(); + ConfigurationNode node = loader.createNode(); + + collectSystemInfo(node.node("system-info")); + + Set alreadyDumped = Collections.newSetFromMap(new WeakHashMap<>()); + + ConfigurationNode dump = node.node("dump"); + for (Object instance : instances) { + Class type = instance.getClass(); + ConfigurationNode instanceDump = dump.node(type.getName()).appendListNode(); + dumpInstance(instance, loader.defaultOptions(), instanceDump, alreadyDumped); + } + + loader.save(node); + + } + + private void dumpInstance(Object instance, ConfigurationOptions options, ConfigurationNode node, Set alreadyDumped) throws SerializationException { + + try { + if (instance == null){ + node.raw(null); + return; + } + + Class type = instance.getClass(); + + if (!alreadyDumped.add(instance)) { + node.set("<<" + instance.toString() + ">>"); + return; + } + + if (instance instanceof Map) { + int count = 0; + Map map = (Map) instance; + + if (map.isEmpty()){ + node.set(map.toString()); + return; + } + + for (Map.Entry entry : map.entrySet()) { + if (++count > 20) { + node.appendListNode().set("<<" + (map.size() - 20) + " more elements>>"); + break; + } + + ConfigurationNode entryNode = node.appendListNode(); + dumpInstance(entry.getKey(), options, entryNode.node("key"), alreadyDumped); + dumpInstance(entry.getValue(), options, entryNode.node("value"), alreadyDumped); + } + return; + } + + if (instance instanceof Collection) { + if (((Collection) instance).isEmpty()){ + node.set(instance.toString()); + return; + } + + int count = 0; + for (Object entry : (Collection) instance) { + if (++count > 20) { + node.appendListNode().set("<<" + (((Collection) instance).size() - 20) + " more elements>>"); + break; + } + + dumpInstance(entry, options, node.appendListNode(), alreadyDumped); + } + return; + } + + if (instance instanceof Object[]) { + if (((Object[]) instance).length == 0){ + node.set(instance.toString()); + return; + } + + int count = 0; + for (Object entry : (Object[]) instance) { + if (++count > 20) { + node.appendListNode().set("<<" + (((Object[]) instance).length - 20) + " more elements>>"); + break; + } + + dumpInstance(entry, options, node.appendListNode(), alreadyDumped); + } + return; + } + + boolean allFields = type.isAnnotationPresent(DebugDump.class); + + boolean foundSomething = false; + for (Field field : type.getDeclaredFields()) { + DebugDump dd = field.getAnnotation(DebugDump.class); + if (dd == null){ + if (!allFields) continue; + if (Modifier.isStatic(field.getModifiers())) continue; + if (Modifier.isTransient(field.getModifiers())) continue; + } + foundSomething = true; + + String key = ""; + if (dd != null) key = dd.value(); + if (key.isEmpty()) key = field.getName(); + + if (options.acceptsType(field.getType())) { + field.setAccessible(true); + node.node(key).set(field.get(instance)); + } else { + field.setAccessible(true); + dumpInstance(field.get(instance), options, node.node(key), alreadyDumped); + } + } + + for (Method method : type.getDeclaredMethods()) { + DebugDump dd = method.getAnnotation(DebugDump.class); + if (dd == null) continue; + foundSomething = true; + + String key = dd.value(); + if (key.isEmpty()) key = method.toGenericString().replace(' ', '_'); + + if (options.acceptsType(method.getReturnType())) { + method.setAccessible(true); + node.node(key).set(method.invoke(instance)); + } else { + method.setAccessible(true); + dumpInstance(method.invoke(instance), options, node.node(key), alreadyDumped); + } + } + + if (!foundSomething) { + node.set(instance.toString()); + } + } catch (Exception ex) { + node.set("Error: " + ex.toString()); + } + } + + private void collectSystemInfo(ConfigurationNode node) throws SerializationException { + node.node("bluemap-version").set(BlueMap.VERSION); + node.node("git-hash").set(BlueMap.GIT_HASH); + node.node("git-clean").set(BlueMap.GIT_CLEAN); + + String[] properties = new String[]{ + "java.runtime.name", + "java.runtime.version", + "java.vm.vendor", + "java.vm.name", + "os.name", + "os.version", + "user.dir", + "java.home", + "file.separator", + "sun.io.unicode.encoding", + "java.class.version" + }; + Map propMap = new HashMap<>(); + for (String key : properties) { + propMap.put(key, System.getProperty(key)); + } + node.node("system-properties").set(propMap); + + node.node("cores").set(Runtime.getRuntime().availableProcessors()); + node.node("max-memory").set(Runtime.getRuntime().maxMemory()); + node.node("total-memory").set(Runtime.getRuntime().totalMemory()); + node.node("free-memory").set(Runtime.getRuntime().freeMemory()); + + node.node("timestamp").set(System.currentTimeMillis()); + node.node("time").set(LocalDateTime.now().toString()); + } + + public static StateDumper global() { + return GLOBAL; + } + + public synchronized void register(Object instance) { + GLOBAL.instances.add(instance); + } + + public synchronized void unregister(Object instance) { + GLOBAL.instances.remove(instance); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java new file mode 100644 index 00000000..7d7bb315 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java @@ -0,0 +1,186 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.map; + +import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.map.hires.HiresModel; +import de.bluecolored.bluemap.core.map.hires.HiresModelManager; +import de.bluecolored.bluemap.core.map.lowres.LowresModelManager; +import de.bluecolored.bluemap.core.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.world.Grid; +import de.bluecolored.bluemap.core.world.World; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Objects; +import java.util.function.Predicate; + +@DebugDump +public class BmMap { + + private final String id; + private final String name; + private final World world; + private final Path fileRoot; + + private final MapRenderState renderState; + + private final HiresModelManager hiresModelManager; + private final LowresModelManager lowresModelManager; + + private Predicate tileFilter; + + private long renderTimeSumNanos; + private long tilesRendered; + + public BmMap(String id, String name, World world, Path fileRoot, ResourcePack resourcePack, MapSettings settings) throws IOException { + this.id = Objects.requireNonNull(id); + this.name = Objects.requireNonNull(name); + this.world = Objects.requireNonNull(world); + this.fileRoot = Objects.requireNonNull(fileRoot); + + Objects.requireNonNull(resourcePack); + Objects.requireNonNull(settings); + + this.renderState = new MapRenderState(); + + File rstateFile = getRenderStateFile(); + if (rstateFile.exists()) { + try { + this.renderState.load(rstateFile); + } catch (IOException ex) { + Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex); + } + } + + this.hiresModelManager = new HiresModelManager( + fileRoot.resolve("hires"), + resourcePack, + settings, + new Grid(settings.getHiresTileSize(), 2) + ); + + this.lowresModelManager = new LowresModelManager( + fileRoot.resolve("lowres"), + new Vector2i(settings.getLowresPointsPerLowresTile(), settings.getLowresPointsPerLowresTile()), + new Vector2i(settings.getLowresPointsPerHiresTile(), settings.getLowresPointsPerHiresTile()), + settings.useGzipCompression() + ); + + this.tileFilter = t -> true; + + this.renderTimeSumNanos = 0; + this.tilesRendered = 0; + } + + public void renderTile(Vector2i tile) { + if (!tileFilter.test(tile)) return; + + long start = System.nanoTime(); + + HiresModel hiresModel = hiresModelManager.render(world, tile); + lowresModelManager.render(hiresModel); + + long end = System.nanoTime(); + long delta = end - start; + + renderTimeSumNanos += delta; + tilesRendered ++; + } + + public synchronized void save() { + lowresModelManager.save(); + + try { + this.renderState.save(getRenderStateFile()); + } catch (IOException ex){ + Logger.global.logError("Failed to save render-state for map: '" + this.id + "'!", ex); + } + } + + public File getRenderStateFile() { + return fileRoot.resolve(".rstate").toFile(); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public World getWorld() { + return world; + } + + public Path getFileRoot() { + return fileRoot; + } + + public MapRenderState getRenderState() { + return renderState; + } + + public HiresModelManager getHiresModelManager() { + return hiresModelManager; + } + + public LowresModelManager getLowresModelManager() { + return lowresModelManager; + } + + public Predicate getTileFilter() { + return tileFilter; + } + + public void setTileFilter(Predicate tileFilter) { + this.tileFilter = tileFilter; + } + + public long getAverageNanosPerTile() { + return renderTimeSumNanos / tilesRendered; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof BmMap) { + BmMap that = (BmMap) obj; + + return this.id.equals(that.id); + } + + return false; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java new file mode 100644 index 00000000..15e60c66 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java @@ -0,0 +1,104 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.map; + +import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.util.AtomicFileHelper; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +@DebugDump +public class MapRenderState { + + private final Map regionRenderTimes; + + public MapRenderState() { + regionRenderTimes = new HashMap<>(); + } + + public synchronized void setRenderTime(Vector2i regionPos, long renderTime) { + regionRenderTimes.put(regionPos, renderTime); + } + + public synchronized long getRenderTime(Vector2i regionPos) { + Long renderTime = regionRenderTimes.get(regionPos); + if (renderTime == null) return -1; + else return renderTime; + } + + public synchronized void reset() { + regionRenderTimes.clear(); + } + + public synchronized void save(File file) throws IOException { + OutputStream fOut = AtomicFileHelper.createFilepartOutputStream(file); + GZIPOutputStream gOut = new GZIPOutputStream(fOut); + + try ( + DataOutputStream dOut = new DataOutputStream(gOut) + ) { + dOut.writeInt(regionRenderTimes.size()); + + for (Map.Entry entry : regionRenderTimes.entrySet()) { + Vector2i regionPos = entry.getKey(); + long renderTime = entry.getValue(); + + dOut.writeInt(regionPos.getX()); + dOut.writeInt(regionPos.getY()); + dOut.writeLong(renderTime); + } + + dOut.flush(); + } + } + + public synchronized void load(File file) throws IOException { + regionRenderTimes.clear(); + + try ( + FileInputStream fIn = new FileInputStream(file); + GZIPInputStream gIn = new GZIPInputStream(fIn); + DataInputStream dIn = new DataInputStream(gIn) + ) { + int size = dIn.readInt(); + + for (int i = 0; i < size; i++) { + Vector2i regionPos = new Vector2i( + dIn.readInt(), + dIn.readInt() + ); + long renderTime = dIn.readLong(); + + regionRenderTimes.put(regionPos, renderTime); + } + } + } + +} diff --git a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapSettings.java similarity index 68% rename from implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapSettings.java index 49de240d..1522a496 100644 --- a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapSettings.java @@ -22,22 +22,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.fabric.events; +package de.bluecolored.bluemap.core.map; -import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.map.hires.RenderSettings; -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.server.world.ServerWorld; +public interface MapSettings extends RenderSettings { -public interface ChunkFinalizeCallback { - Event EVENT = EventFactory.createArrayBacked(ChunkFinalizeCallback.class, - (listeners) -> (world, chunkPos) -> { - for (ChunkFinalizeCallback event : listeners) { - event.onChunkFinalized(world, chunkPos); - } - } - ); + int getHiresTileSize(); + + int getLowresPointsPerLowresTile(); + + int getLowresPointsPerHiresTile(); - void onChunkFinalized(ServerWorld world, Vector2i chunkPos); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModel.java similarity index 90% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModel.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModel.java index 72471916..bbe01eb6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModel.java @@ -22,31 +22,27 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.hires; +package de.bluecolored.bluemap.core.map.hires; -import java.util.UUID; - -import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector4f; - import de.bluecolored.bluemap.core.model.ExtendedModel; +import java.util.UUID; + /** * A model, containing additional information about the tile it represents */ public class HiresModel extends ExtendedModel { private UUID world; - private Vector2i tile; private Vector3i blockMin, blockMax, blockSize; private int[][] heights; private Vector4f[][] colors; - public HiresModel(UUID world, Vector2i tile, Vector3i blockMin, Vector3i blockMax) { + public HiresModel(UUID world, Vector3i blockMin, Vector3i blockMax) { this.world = world; - this.tile = tile; this.blockMin = blockMin; this.blockMax = blockMax; this.blockSize = blockMax.sub(blockMin).add(Vector3i.ONE); @@ -89,8 +85,4 @@ public class HiresModel extends ExtendedModel { return blockSize; } - public Vector2i getTile(){ - return tile; - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java similarity index 52% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java index 6b2c8f6f..43f177a6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java @@ -22,46 +22,39 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.hires; +package de.bluecolored.bluemap.core.map.hires; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.render.RenderSettings; -import de.bluecolored.bluemap.core.render.WorldTile; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.util.AABB; +import de.bluecolored.bluemap.core.util.AtomicFileHelper; import de.bluecolored.bluemap.core.util.FileUtils; +import de.bluecolored.bluemap.core.world.Grid; +import de.bluecolored.bluemap.core.world.World; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; import java.util.zip.GZIPOutputStream; public class HiresModelManager { - private Path fileRoot; - private HiresModelRenderer renderer; - - private Vector2i tileSize; - private Vector2i gridOrigin; - - private boolean useGzip; - - public HiresModelManager(Path fileRoot, ResourcePack resourcePack, RenderSettings renderSettings, Vector2i tileSize) { - this(fileRoot, new HiresModelRenderer(resourcePack, renderSettings), tileSize, new Vector2i(2, 2), renderSettings.useGzipCompression()); + private final Path fileRoot; + private final HiresModelRenderer renderer; + private final Grid tileGrid; + private final boolean useGzip; + + public HiresModelManager(Path fileRoot, ResourcePack resourcePack, RenderSettings renderSettings, Grid tileGrid) { + this(fileRoot, new HiresModelRenderer(resourcePack, renderSettings), tileGrid, renderSettings.useGzipCompression()); } - - public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i tileSize, Vector2i gridOrigin, boolean useGzip) { + + public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Grid tileGrid, boolean useGzip) { this.fileRoot = fileRoot; this.renderer = renderer; - - this.tileSize = tileSize; - this.gridOrigin = gridOrigin; + + this.tileGrid = tileGrid; this.useGzip = useGzip; } @@ -69,24 +62,28 @@ public class HiresModelManager { /** * Renders the given world tile with the provided render-settings */ - public HiresModel render(WorldTile tile) { - HiresModel model = renderer.render(tile, getTileRegion(tile)); - save(model); + public HiresModel render(World world, Vector2i tile) { + Vector2i tileMin = tileGrid.getCellMin(tile); + Vector2i tileMax = tileGrid.getCellMax(tile); + + Vector3i modelMin = new Vector3i(tileMin.getX(), Integer.MIN_VALUE, tileMin.getY()); + Vector3i modelMax = new Vector3i(tileMax.getX(), Integer.MAX_VALUE, tileMax.getY()); + + HiresModel model = renderer.render(world, modelMin, modelMax); + save(model, tile); return model; } - private void save(final HiresModel model) { + private void save(final HiresModel model, Vector2i tile) { final String modelJson = model.toBufferGeometry().toJson(); - save(model, modelJson); + save(modelJson, tile); } - private void save(HiresModel model, String modelJson){ - File file = getFile(model.getTile(), useGzip); + private void save(String modelJson, Vector2i tile){ + File file = getFile(tile, useGzip); try { - FileUtils.createFile(file); - - OutputStream os = new FileOutputStream(file); + OutputStream os = new BufferedOutputStream(AtomicFileHelper.createFilepartOutputStream(file)); if (useGzip) os = new GZIPOutputStream(os); OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); try ( @@ -102,75 +99,24 @@ public class HiresModelManager { } /** - * Returns all tiles that the provided chunks are intersecting + * Returns the tile-grid */ - public Collection getTilesForChunks(Iterable chunks){ - Set tiles = new HashSet<>(); - for (Vector2i chunk : chunks) { - Vector3i minBlockPos = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16); - - //loop to cover the case that a tile is smaller then a chunk, should normally only add one tile (at 0, 0) - for (int x = 0; x < 15; x += getTileSize().getX()) { - for (int z = 0; z < 15; z += getTileSize().getY()) { - tiles.add(posToTile(minBlockPos.add(x, 0, z))); - } - } - - tiles.add(posToTile(minBlockPos.add(0, 0, 15))); - tiles.add(posToTile(minBlockPos.add(15, 0, 0))); - tiles.add(posToTile(minBlockPos.add(15, 0, 15))); - } - - return tiles; - } - - /** - * Returns the region of blocks that a tile includes - */ - public AABB getTileRegion(WorldTile tile) { - Vector3i min = new Vector3i( - tile.getTile().getX() * tileSize.getX() + gridOrigin.getX(), - 0, - tile.getTile().getY() * tileSize.getY() + gridOrigin.getY() - ); - Vector3i max = min.add( - tileSize.getX() - 1, - 255, - tileSize.getY() - 1 - ); - return new AABB(min, max); - } - - /** - * Returns the tile-size - */ - public Vector2i getTileSize() { - return tileSize; - } - - /** - * Returns the grid-origin - */ - public Vector2i getGridOrigin() { - return gridOrigin; + public Grid getTileGrid() { + return tileGrid; } /** * Converts a block-position to a map-tile-coordinate */ public Vector2i posToTile(Vector3i pos){ - return posToTile(pos.toDouble()); + return tileGrid.getCell(pos.toVector2(true)); } /** * Converts a block-position to a map-tile-coordinate */ public Vector2i posToTile(Vector3d pos){ - pos = pos.sub(new Vector3d(gridOrigin.getX(), 0.0, gridOrigin.getY())); - return Vector2i.from( - (int) Math.floor(pos.getX() / getTileSize().getX()), - (int) Math.floor(pos.getZ() / getTileSize().getY()) - ); + return tileGrid.getCell(new Vector2i(pos.getFloorX(), pos.getFloorZ())); } /** diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java similarity index 77% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java index 5e70ccd3..daaefd6f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java @@ -22,19 +22,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.hires; +package de.bluecolored.bluemap.core.map.hires; import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector4f; - -import de.bluecolored.bluemap.core.render.RenderSettings; -import de.bluecolored.bluemap.core.render.WorldTile; -import de.bluecolored.bluemap.core.render.hires.blockmodel.BlockStateModel; -import de.bluecolored.bluemap.core.render.hires.blockmodel.BlockStateModelFactory; +import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModel; +import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory; import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.util.AABB; import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; @@ -50,35 +47,31 @@ public class HiresModelRenderer { public HiresModelRenderer(ResourcePack resourcePack, RenderSettings renderSettings) { this.renderSettings = renderSettings; this.modelFactory = new BlockStateModelFactory(resourcePack, renderSettings); - - switch (resourcePack.getMinecraftVersion()) { - case MC_1_12: - grassId = "minecraft:tall_grass"; - break; - default: - grassId = "minecraft:grass"; - break; + + if (resourcePack.getMinecraftVersion().isBefore(MinecraftVersion.THE_FLATTENING)) { + grassId = "minecraft:tall_grass"; + } else { + grassId = "minecraft:grass"; } } - public HiresModel render(WorldTile tile, AABB region) { - Vector3i modelMin = region.getMin(); - Vector3i modelMax = region.getMax(); - + public HiresModel render(World world, Vector3i modelMin, Vector3i modelMax) { Vector3i min = modelMin.max(renderSettings.getMin()); Vector3i max = modelMax.min(renderSettings.getMax()); + Vector3f modelAnchor = new Vector3f(modelMin.getX(), 0, modelMin.getZ()); - World world = tile.getWorld(); - - HiresModel model = new HiresModel(tile.getWorld().getUUID(), tile.getTile(), modelMin, modelMax); + HiresModel model = new HiresModel(world.getUUID(), modelMin, modelMax); for (int x = min.getX(); x <= max.getX(); x++){ for (int z = min.getZ(); z <= max.getZ(); z++){ int maxHeight = 0; Vector4f color = Vector4f.ZERO; - - for (int y = min.getY(); y <= max.getY(); y++){ + + int minY = Math.max(min.getY(), world.getMinY(x, z)); + int maxY = Math.min(max.getY(), world.getMaxY(x, z)); + + for (int y = minY; y <= maxY; y++){ Block block = world.getBlock(x, y, z); if (block.getBlockState().equals(BlockState.AIR)) continue; @@ -94,8 +87,12 @@ public class HiresModelRenderer { } //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())); + + // skip empty blocks + if (blockModel.getFaces().isEmpty()) continue; + + // move block-model to correct position + blockModel.translate(new Vector3f(x - modelAnchor.getX(), y - modelAnchor.getY(), z - modelAnchor.getZ())); //update color and height (only if not 100% translucent) Vector4f blockColor = blockModel.getMapColor(); @@ -104,7 +101,7 @@ public class HiresModelRenderer { color = MathUtils.overlayColors(blockModel.getMapColor(), color); } - //TODO: quick hack to random offset grass + //quick hack to random offset grass if (block.getBlockState().getFullId().equals(grassId)){ float dx = (MathUtils.hashToFloat(x, y, z, 123984) - 0.5f) * 0.75f; float dz = (MathUtils.hashToFloat(x, y, z, 345542) - 0.5f) * 0.75f; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java similarity index 85% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java index 3e8a11fc..d827ee0a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java @@ -22,14 +22,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render; +package de.bluecolored.bluemap.core.map.hires; import com.flowpowered.math.vector.Vector3i; public interface RenderSettings { - static final Vector3i DEFAULT_MIN = Vector3i.from(Integer.MIN_VALUE); - static final Vector3i DEFAULT_MAX = Vector3i.from(Integer.MAX_VALUE); + Vector3i DEFAULT_MIN = Vector3i.from(Integer.MIN_VALUE); + Vector3i DEFAULT_MAX = Vector3i.from(Integer.MAX_VALUE); /** * Whether faces that have a sky-light-value of 0 will be rendered or not. @@ -68,13 +68,4 @@ public interface RenderSettings { return true; } - default RenderSettings copy() { - return new StaticRenderSettings( - isExcludeFacesWithoutSunlight(), - getMin(), - getMax(), - isRenderEdges() - ); - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModel.java similarity index 97% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModel.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModel.java index 27b5586d..0cc13514 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModel.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.hires.blockmodel; +package de.bluecolored.bluemap.core.map.hires.blockmodel; import com.flowpowered.math.vector.Vector4f; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java similarity index 96% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java index 74a4670c..9fdb5110 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java @@ -22,9 +22,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.hires.blockmodel; +package de.bluecolored.bluemap.core.map.hires.blockmodel; -import de.bluecolored.bluemap.core.render.RenderSettings; +import de.bluecolored.bluemap.core.map.hires.RenderSettings; import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; import de.bluecolored.bluemap.core.resourcepack.BlockStateResource; import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java similarity index 93% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java index 82c5fd5a..64b30b50 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java @@ -22,20 +22,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.hires.blockmodel; - -import java.util.HashSet; +package de.bluecolored.bluemap.core.map.hires.blockmodel; import com.flowpowered.math.matrix.Matrix3f; import com.flowpowered.math.vector.Vector2f; import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector4f; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.map.hires.RenderSettings; import de.bluecolored.bluemap.core.model.ExtendedFace; import de.bluecolored.bluemap.core.model.ExtendedModel; -import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; import de.bluecolored.bluemap.core.resourcepack.BlockModelResource; import de.bluecolored.bluemap.core.resourcepack.Texture; @@ -44,31 +40,36 @@ import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.Arrays; +import java.util.HashSet; + /** * A model builder for all liquid blocks */ public class LiquidModelBuilder { - private static final HashSet DEFAULT_WATERLOGGED_BLOCK_IDS = Sets.newHashSet( + private static final HashSet DEFAULT_WATERLOGGED_BLOCK_IDS = new HashSet<>(Arrays.asList( "minecraft:seagrass", "minecraft:tall_seagrass", "minecraft:kelp", "minecraft:kelp_plant", "minecraft:bubble_column" - ); + )); - private BlockState liquidBlockState; - private Block block; - private MinecraftVersion minecraftVersion; - private RenderSettings renderSettings; - private BlockColorCalculator colorCalculator; + private final BlockState liquidBlockState; + private final Block block; + private final RenderSettings renderSettings; + private final BlockColorCalculator colorCalculator; + + private final boolean useWaterColorMap; public LiquidModelBuilder(Block block, BlockState liquidBlockState, MinecraftVersion minecraftVersion, RenderSettings renderSettings, BlockColorCalculator colorCalculator) { this.block = block; - this.minecraftVersion = minecraftVersion; this.renderSettings = renderSettings; this.liquidBlockState = liquidBlockState; this.colorCalculator = colorCalculator; + + this.useWaterColorMap = minecraftVersion.isAtLeast(new MinecraftVersion(1, 13)); } public BlockStateModel build(TransformedBlockModelResource bmr) { @@ -109,7 +110,7 @@ public class LiquidModelBuilder { int textureId = texture.getId(); Vector3f tintcolor = Vector3f.ONE; - if (minecraftVersion != MinecraftVersion.MC_1_12 && liquidBlockState.getFullId().equals("minecraft:water")) { + if (useWaterColorMap && liquidBlockState.getFullId().equals("minecraft:water")) { tintcolor = colorCalculator.getWaterAverageColor(block); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java index b5515cd4..610a54fe 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.hires.blockmodel; +package de.bluecolored.bluemap.core.map.hires.blockmodel; import com.flowpowered.math.TrigMath; import com.flowpowered.math.imaginary.Complexf; @@ -34,7 +34,7 @@ import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector4f; import de.bluecolored.bluemap.core.model.ExtendedFace; -import de.bluecolored.bluemap.core.render.RenderSettings; +import de.bluecolored.bluemap.core.map.hires.RenderSettings; import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; import de.bluecolored.bluemap.core.resourcepack.BlockModelResource; import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Rotation; @@ -171,7 +171,7 @@ public class ResourceModelBuilder { Quaternionf rot = Quaternionf.fromAxesAnglesDeg(rotation.getX(), rotation.getY(), 0); uvLockAngle = (int) rot.getAxesAnglesDeg().dot(faceDir.toVector().toFloat()); - //TODO: my math has stopped working, there has to be a more consistent solution + //my math has stopped working, there has to be a more consistent solution for this... if (rotation.getX() >= 180 && rotation.getY() != 90 && rotation.getY() != 270) uvLockAngle += 180; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java similarity index 78% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java index 871da8cc..de48ff9e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java @@ -22,32 +22,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.lowres; +package de.bluecolored.bluemap.core.map.lowres; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3f; import de.bluecolored.bluemap.core.threejs.BufferGeometry; +import de.bluecolored.bluemap.core.util.AtomicFileHelper; import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.util.ModelUtils; import java.io.*; import java.nio.charset.StandardCharsets; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.zip.GZIPOutputStream; public class LowresModel { - - private UUID world; - private Vector2i tilePos; - private BufferGeometry model; - + + private final BufferGeometry model; private Map changes; private boolean hasUnsavedChanges; @@ -56,17 +49,13 @@ public class LowresModel { fileLock = new Object(), modelLock = new Object(); - public LowresModel(UUID world, Vector2i tilePos, Vector2i gridSize) { + public LowresModel(Vector2i gridSize) { this( - world, - tilePos, ModelUtils.makeGrid(gridSize).toBufferGeometry() ); } - public LowresModel(UUID world, Vector2i tilePos, BufferGeometry model) { - this.world = world; - this.tilePos = tilePos; + public LowresModel(BufferGeometry model) { this.model = model; this.changes = new ConcurrentHashMap<>(); @@ -103,27 +92,14 @@ public class LowresModel { } synchronized (fileLock) { - FileUtils.createFile(file); - - try { - FileUtils.waitForFile(file, 10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Failed to get write-access to file: " + file, e); - } catch (TimeoutException e) { - throw new IOException("Failed to get write-access to file: " + file, e); - } - - OutputStream os = new FileOutputStream(file); + OutputStream os = new BufferedOutputStream(AtomicFileHelper.createFilepartOutputStream(file)); if (useGzip) os = new GZIPOutputStream(os); OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); try ( PrintWriter pw = new PrintWriter(osw); ){ pw.print(json); - pw.flush(); } - } } @@ -134,7 +110,7 @@ public class LowresModel { if (changes.isEmpty()) return; Map points = changes; - changes = new HashMap<>(); + changes = new ConcurrentHashMap<>(); float[] position = model.attributes.get("position").values(); float[] color = model.attributes.get("color").values(); @@ -178,36 +154,12 @@ public class LowresModel { return model; } - public UUID getWorld(){ - return world; - } - - public Vector2i getTile(){ - return tilePos; - } - - @Override - public int hashCode() { - return Objects.hash(world, tilePos); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof LowresModel){ - LowresModel other = (LowresModel) obj; - if (!other.world.equals(world)) return false; - if (other.tilePos.equals(tilePos)) return true; - } - - return false; - } - /** * a point on this lowres-model-grid */ - public class LowresPoint { - private float height; - private Vector3f color; + public static class LowresPoint { + private final float height; + private final Vector3f color; public LowresPoint(float height, Vector3f color) { this.height = height; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java similarity index 84% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java index 4b472fb0..7cc3f154 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java @@ -22,11 +22,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render.lowres; +package de.bluecolored.bluemap.core.map.lowres; import com.flowpowered.math.vector.*; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.render.hires.HiresModel; +import de.bluecolored.bluemap.core.map.hires.HiresModel; import de.bluecolored.bluemap.core.threejs.BufferGeometry; import de.bluecolored.bluemap.core.util.FileUtils; import org.apache.commons.io.IOUtils; @@ -47,19 +47,17 @@ import java.util.zip.GZIPInputStream; public class LowresModelManager { - private Path fileRoot; - - private Vector2i gridSize; - private Vector2i pointsPerHiresTile; - - private Map models; - - private boolean useGzip; + private final Path fileRoot; + private final Vector2i pointsPerLowresTile; + private final Vector2i pointsPerHiresTile; + private final boolean useGzip; + + private final Map models; - public LowresModelManager(Path fileRoot, Vector2i gridSize, Vector2i pointsPerHiresTile, boolean useGzip) { + public LowresModelManager(Path fileRoot, Vector2i pointsPerLowresTile, Vector2i pointsPerHiresTile, boolean useGzip) { this.fileRoot = fileRoot; - this.gridSize = gridSize; + this.pointsPerLowresTile = pointsPerLowresTile; this.pointsPerHiresTile = pointsPerHiresTile; models = new ConcurrentHashMap<>(); @@ -68,7 +66,7 @@ public class LowresModelManager { } /** - * Renders all points from the given highres-model onto the lowres-grid + * Renders all points from the given hires-model onto the lowres-grid */ public void render(HiresModel hiresModel) { Vector3i min = hiresModel.getBlockMin(); @@ -124,15 +122,15 @@ public class LowresModelManager { * Saves all unsaved changes to the models to disk */ public synchronized void save(){ - for (CachedModel model : models.values()){ - saveModel(model); + for (Entry entry : models.entrySet()){ + saveModel(entry.getKey(), entry.getValue()); } tidyUpModelCache(); } /** - * Updates a point on the lowresmodel-grid + * Updates a point on the lowres-model-grid */ public void update(UUID world, Vector2i point, float height, Vector3f color) { Vector2i tile = pointToTile(point); @@ -186,9 +184,9 @@ public class LowresModelManager { String json = IOUtils.toString(is, StandardCharsets.UTF_8); - model = new CachedModel(world, tile, BufferGeometry.fromJson(json)); + model = new CachedModel(BufferGeometry.fromJson(json)); } catch (IllegalArgumentException | IOException ex){ - Logger.global.logError("Failed to load lowres model: " + modelFile, ex); + Logger.global.logWarning("Failed to load lowres model '" + modelFile + "': " + ex); try { FileUtils.delete(modelFile); @@ -199,7 +197,7 @@ public class LowresModelManager { } if (model == null){ - model = new CachedModel(world, tile, gridSize); + model = new CachedModel(pointsPerLowresTile); } models.put(modelFile, model); @@ -227,18 +225,17 @@ public class LowresModelManager { int size = entries.size(); for (Entry e : entries) { if (size > 10) { - saveAndRemoveModel(e.getValue()); + saveAndRemoveModel(e.getKey(), e.getValue()); continue; } if (e.getValue().getCacheTime() > 120000) { - saveModel(e.getValue()); + saveModel(e.getKey(), e.getValue()); } } } - private synchronized void saveAndRemoveModel(CachedModel model) { - File modelFile = getFile(model.getTile(), useGzip); + private synchronized void saveAndRemoveModel(File modelFile, CachedModel model) { models.remove(modelFile); try { model.save(modelFile, false, useGzip); @@ -248,8 +245,7 @@ public class LowresModelManager { } } - private void saveModel(CachedModel model) { - File modelFile = getFile(model.getTile(), useGzip); + private void saveModel(File modelFile, CachedModel model) { try { model.save(modelFile, false, useGzip); //logger.logDebug("Saved lowres tile: " + model.getTile()); @@ -263,35 +259,35 @@ public class LowresModelManager { private Vector2i pointToTile(Vector2i point){ return point .toDouble() - .div(gridSize.toDouble()) + .div(pointsPerLowresTile.toDouble()) .floor() .toInt(); } private Vector2i getPointRelativeToTile(Vector2i tile, Vector2i point){ - return point.sub(tile.mul(gridSize)); + return point.sub(tile.mul(pointsPerLowresTile)); } public Vector2i getTileSize() { - return gridSize; + return pointsPerLowresTile; } public Vector2i getPointsPerHiresTile() { return pointsPerHiresTile; } - private class CachedModel extends LowresModel { + private static class CachedModel extends LowresModel { private long cacheTime; - public CachedModel(UUID world, Vector2i tilePos, BufferGeometry model) { - super(world, tilePos, model); + public CachedModel(BufferGeometry model) { + super(model); cacheTime = System.currentTimeMillis(); } - public CachedModel(UUID world, Vector2i tilePos, Vector2i gridSize) { - super(world, tilePos, gridSize); + public CachedModel(Vector2i gridSize) { + super(gridSize); cacheTime = System.currentTimeMillis(); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java index 6432f0f3..f606140e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java @@ -24,10 +24,7 @@ */ package de.bluecolored.bluemap.core.mca; -import java.util.Arrays; - import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper; import de.bluecolored.bluemap.core.world.Biome; @@ -36,11 +33,14 @@ import de.bluecolored.bluemap.core.world.LightData; import net.querz.nbt.CompoundTag; import net.querz.nbt.ListTag; import net.querz.nbt.NumberTag; -import net.querz.nbt.mca.MCAUtil; -public class ChunkAnvil112 extends Chunk { - private BlockIdMapper blockIdMapper; - private BiomeMapper biomeIdMapper; +import java.util.Arrays; +import java.util.function.IntFunction; + +public class ChunkAnvil112 extends MCAChunk { + private final BiomeMapper biomeIdMapper; + private final BlockIdMapper blockIdMapper; + private final IntFunction forgeBlockIdMapper; private boolean isGenerated; private boolean hasLight; @@ -48,11 +48,12 @@ public class ChunkAnvil112 extends Chunk { private byte[] biomes; @SuppressWarnings("unchecked") - public ChunkAnvil112(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) { - super(world, chunkTag); + public ChunkAnvil112(CompoundTag chunkTag, boolean ignoreMissingLightData, BiomeMapper biomeIdMapper, BlockIdMapper blockIdMapper, IntFunction forgeBlockIdMapper) { + super(chunkTag); - blockIdMapper = getWorld().getBlockIdMapper(); - biomeIdMapper = getWorld().getBiomeIdMapper(); + this.blockIdMapper = blockIdMapper; + this.biomeIdMapper = biomeIdMapper; + this.forgeBlockIdMapper = forgeBlockIdMapper; CompoundTag levelData = chunkTag.getCompoundTag("Level"); @@ -88,7 +89,8 @@ public class ChunkAnvil112 extends Chunk { @Override public BlockState getBlockState(Vector3i pos) { - int sectionY = MCAUtil.blockToChunk(pos.getY()); + int sectionY = pos.getY() >> 4; + if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR; Section section = this.sections[sectionY]; if (section == null) return BlockState.AIR; @@ -97,7 +99,8 @@ public class ChunkAnvil112 extends Chunk { } public String getBlockIdMeta(Vector3i pos) { - int sectionY = MCAUtil.blockToChunk(pos.getY()); + int sectionY = pos.getY() >> 4; + if (sectionY < 0 || sectionY >= this.sections.length) return "0:0"; Section section = this.sections[sectionY]; if (section == null) return "0:0"; @@ -108,8 +111,10 @@ public class ChunkAnvil112 extends Chunk { @Override public LightData getLightData(Vector3i pos) { if (!hasLight) return LightData.SKY; - - int sectionY = MCAUtil.blockToChunk(pos.getY()); + + int sectionY = pos.getY() >> 4; + if (sectionY < 0 || sectionY >= this.sections.length) + return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY; Section section = this.sections[sectionY]; if (section == null) return LightData.SKY; @@ -118,11 +123,12 @@ public class ChunkAnvil112 extends Chunk { } @Override - public Biome getBiome(Vector3i pos) { - int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) - int z = pos.getZ() & 0xF; + public Biome getBiome(int x, int y, int z) { + x = x & 0xF; // Math.floorMod(pos.getX(), 16) + z = z & 0xF; int biomeByteIndex = z * 16 + x; - + + if (biomeByteIndex >= this.biomes.length) return Biome.DEFAULT; return biomeIdMapper.get(biomes[biomeByteIndex] & 0xFF); } @@ -168,7 +174,7 @@ public class ChunkAnvil112 extends Chunk { int blockData = getByteHalf(this.data[blockHalfByteIndex], largeHalf); - String forgeIdMapping = getWorld().getForgeBlockIdMapping(blockId); + String forgeIdMapping = forgeBlockIdMapper.apply(blockId); if (forgeIdMapping != null) { return blockIdMapper.get(forgeIdMapping, blockId, blockData); } else { @@ -191,7 +197,7 @@ public class ChunkAnvil112 extends Chunk { } int blockData = getByteHalf(this.data[blockHalfByteIndex], largeHalf); - String forgeIdMapping = getWorld().getForgeBlockIdMapping(blockId); + String forgeIdMapping = forgeBlockIdMapper.apply(blockId); return blockId + ":" + blockData + " " + forgeIdMapping; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java index 9937eaf7..76437095 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java @@ -24,22 +24,20 @@ */ package de.bluecolored.bluemap.core.mca; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; 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 net.querz.nbt.mca.MCAUtil; -public class ChunkAnvil113 extends Chunk { +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class ChunkAnvil113 extends MCAChunk { private BiomeMapper biomeIdMapper; private boolean isGenerated; @@ -48,16 +46,16 @@ public class ChunkAnvil113 extends Chunk { private int[] biomes; @SuppressWarnings("unchecked") - public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) { - super(world, chunkTag); + public ChunkAnvil113(CompoundTag chunkTag, boolean ignoreMissingLightData, BiomeMapper biomeIdMapper) { + super(chunkTag); - biomeIdMapper = getWorld().getBiomeIdMapper(); + this.biomeIdMapper = biomeIdMapper; CompoundTag levelData = chunkTag.getCompoundTag("Level"); String status = levelData.getString("Status"); - isGenerated = status.equals("full") || status.equals("spawn"); // full is normal fully generated and spawn seems to be converted from old format but not yet loaded if you optimized your world - hasLight = isGenerated; + this.isGenerated = status.equals("full"); + this.hasLight = isGenerated; if (!isGenerated && ignoreMissingLightData) { isGenerated = !status.equals("empty"); @@ -100,7 +98,8 @@ public class ChunkAnvil113 extends Chunk { @Override public BlockState getBlockState(Vector3i pos) { - int sectionY = MCAUtil.blockToChunk(pos.getY()); + int sectionY = pos.getY() >> 4; + if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR; Section section = this.sections[sectionY]; if (section == null) return BlockState.AIR; @@ -111,8 +110,10 @@ public class ChunkAnvil113 extends Chunk { @Override public LightData getLightData(Vector3i pos) { if (!hasLight) return LightData.SKY; - - int sectionY = MCAUtil.blockToChunk(pos.getY()); + + int sectionY = pos.getY() >> 4; + if (sectionY < 0 || sectionY >= this.sections.length) + return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY; Section section = this.sections[sectionY]; if (section == null) return LightData.SKY; @@ -121,11 +122,12 @@ public class ChunkAnvil113 extends Chunk { } @Override - public Biome getBiome(Vector3i pos) { - int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) - int z = pos.getZ() & 0xF; + public Biome getBiome(int x, int y, int z) { + x = x & 0xF; // Math.floorMod(pos.getX(), 16) + z = z & 0xF; int biomeIntIndex = z * 16 + x; - + + if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT; return biomeIdMapper.get(biomes[biomeIntIndex]); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java index 57e2d44f..4e9ecc9b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java @@ -24,22 +24,20 @@ */ package de.bluecolored.bluemap.core.mca; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; 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 net.querz.nbt.mca.MCAUtil; -public class ChunkAnvil115 extends Chunk { +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class ChunkAnvil115 extends MCAChunk { private BiomeMapper biomeIdMapper; private boolean isGenerated; @@ -48,21 +46,21 @@ public class ChunkAnvil115 extends Chunk { private int[] biomes; @SuppressWarnings("unchecked") - public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) { - super(world, chunkTag); + public ChunkAnvil115(CompoundTag chunkTag, boolean ignoreMissingLightData, BiomeMapper biomeIdMapper) { + super(chunkTag); - biomeIdMapper = getWorld().getBiomeIdMapper(); + this.biomeIdMapper = biomeIdMapper; CompoundTag levelData = chunkTag.getCompoundTag("Level"); String status = levelData.getString("Status"); - isGenerated = status.equals("full") || status.equals("spawn"); // full is normal fully generated and spawn seems to be converted from old format but not yet loaded if you optimized your world - hasLight = isGenerated; + this.isGenerated = status.equals("full"); + this.hasLight = isGenerated; if (!isGenerated && ignoreMissingLightData) { isGenerated = !status.equals("empty"); } - + 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) levelData.getListTag("Sections"))) { @@ -81,7 +79,7 @@ public class ChunkAnvil115 extends Chunk { } } else if (tag instanceof IntArrayTag) { - biomes = ((IntArrayTag) tag).getValue(); + biomes = ((IntArrayTag) tag).getValue(); } if (biomes == null || biomes.length == 0) { @@ -100,7 +98,8 @@ public class ChunkAnvil115 extends Chunk { @Override public BlockState getBlockState(Vector3i pos) { - int sectionY = MCAUtil.blockToChunk(pos.getY()); + int sectionY = pos.getY() >> 4; + if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR; Section section = this.sections[sectionY]; if (section == null) return BlockState.AIR; @@ -111,8 +110,10 @@ public class ChunkAnvil115 extends Chunk { @Override public LightData getLightData(Vector3i pos) { if (!hasLight) return LightData.SKY; - - int sectionY = MCAUtil.blockToChunk(pos.getY()); + + int sectionY = pos.getY() >> 4; + if (sectionY < 0 || sectionY >= this.sections.length) + return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY; Section section = this.sections[sectionY]; if (section == null) return LightData.SKY; @@ -121,16 +122,17 @@ public class ChunkAnvil115 extends Chunk { } @Override - public Biome getBiome(Vector3i pos) { - int x = (pos.getX() & 0xF) / 4; // Math.floorMod(pos.getX(), 16) - int z = (pos.getZ() & 0xF) / 4; - int y = pos.getY() / 4; + public Biome 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 >= this.biomes.length) return Biome.DEFAULT; return biomeIdMapper.get(biomes[biomeIntIndex]); } - private class Section { + private static class Section { private static final String AIR_ID = "minecraft:air"; private int sectionY; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java index f16bb66f..b64aeaec 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java @@ -24,72 +24,76 @@ */ package de.bluecolored.bluemap.core.mca; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; 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 net.querz.nbt.mca.MCAUtil; -public class ChunkAnvil116 extends Chunk { +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class ChunkAnvil116 extends MCAChunk { private BiomeMapper biomeIdMapper; private boolean isGenerated; private boolean hasLight; - private Section[] sections; + private Map sections; + private int sectionMin, sectionMax; private int[] biomes; @SuppressWarnings("unchecked") - public ChunkAnvil116(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) { - super(world, chunkTag); + public ChunkAnvil116(CompoundTag chunkTag, boolean ignoreMissingLightData, BiomeMapper biomeIdMapper) { + super(chunkTag); - biomeIdMapper = getWorld().getBiomeIdMapper(); + this.biomeIdMapper = biomeIdMapper; CompoundTag levelData = chunkTag.getCompoundTag("Level"); String status = levelData.getString("Status"); - isGenerated = status.equals("full") || status.equals("spawn"); // full is normal fully generated and spawn seems to be converted from old format but not yet loaded if you optimized your world - hasLight = isGenerated; + this.isGenerated = status.equals("full"); + this.hasLight = isGenerated; if (!isGenerated && ignoreMissingLightData) { isGenerated = !status.equals("empty"); } - - 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? + + this.sections = new HashMap<>(); // Is using a has-map the fastest/best way for an int->Object mapping? + this.sectionMin = Integer.MAX_VALUE; + this.sectionMax = Integer.MIN_VALUE; if (levelData.containsKey("Sections")) { for (CompoundTag sectionTag : ((ListTag) levelData.getListTag("Sections"))) { + if (sectionTag.getListTag("Palette") == null) continue; // ignore empty sections + Section section = new Section(sectionTag); - if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section; + int y = section.getSectionY(); + + if (sectionMin > y) sectionMin = y; + if (sectionMax < y) sectionMax = y; + + sections.put(y, section); } } 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]; + this.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(); + this.biomes = ((IntArrayTag) tag).getValue(); } - if (biomes == null || biomes.length == 0) { - biomes = new int[1024]; - } - - if (biomes.length < 1024) { - biomes = Arrays.copyOf(biomes, 1024); + if (biomes == null) { + this.biomes = new int[0]; } } @@ -100,9 +104,9 @@ public class ChunkAnvil116 extends Chunk { @Override public BlockState getBlockState(Vector3i pos) { - int sectionY = MCAUtil.blockToChunk(pos.getY()); + int sectionY = pos.getY() >> 4; - Section section = this.sections[sectionY]; + Section section = this.sections.get(sectionY); if (section == null) return BlockState.AIR; return section.getBlockState(pos); @@ -112,25 +116,41 @@ public class ChunkAnvil116 extends Chunk { public LightData getLightData(Vector3i pos) { if (!hasLight) return LightData.SKY; - int sectionY = MCAUtil.blockToChunk(pos.getY()); - - Section section = this.sections[sectionY]; - if (section == null) return LightData.SKY; + int sectionY = pos.getY() >> 4; + + Section section = this.sections.get(sectionY); + if (section == null) return (sectionY < sectionMin) ? LightData.ZERO : LightData.SKY; return section.getLightData(pos); } @Override - public Biome getBiome(Vector3i pos) { - int x = (pos.getX() & 0xF) / 4; // Math.floorMod(pos.getX(), 16) - int z = (pos.getZ() & 0xF) / 4; - int y = pos.getY() / 4; - int biomeIntIndex = y * 16 + z * 4 + x; + public Biome getBiome(int x, int y, int z) { + if (biomes.length < 16) return Biome.DEFAULT; + + 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 biomeIdMapper.get(biomes[biomeIntIndex]); } - - private class Section { + + @Override + public int getMinY(int x, int z) { + return sectionMin * 16; + } + + @Override + public int getMaxY(int x, int z) { + return sectionMax * 16 + 15; + } + + private static class Section { private static final String AIR_ID = "minecraft:air"; private int sectionY; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/EmptyChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/EmptyChunk.java index bcfb66ca..4820ae77 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/EmptyChunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/EmptyChunk.java @@ -26,16 +26,13 @@ package de.bluecolored.bluemap.core.mca; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.LightData; -public class EmptyChunk extends Chunk { +public class EmptyChunk extends MCAChunk { - protected EmptyChunk(MCAWorld world, Vector2i chunkPos) { - super(world, chunkPos); - } + public static final MCAChunk INSTANCE = new EmptyChunk(); @Override public boolean isGenerated() { @@ -53,7 +50,7 @@ public class EmptyChunk extends Chunk { } @Override - public Biome getBiome(Vector3i pos) { + public Biome getBiome(int x, int y, int z) { return Biome.DEFAULT; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java similarity index 61% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java index 81c9e22d..641b7757 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java @@ -24,53 +24,30 @@ */ package de.bluecolored.bluemap.core.mca; -import java.io.IOException; - -import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.core.world.Biome; 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; -public abstract class Chunk { - - private final MCAWorld world; - private final Vector2i chunkPos; - +import java.io.IOException; + +public abstract class MCAChunk implements Chunk { + private final int dataVersion; - protected Chunk(MCAWorld world, Vector2i chunkPos) { - this.world = world; - this.chunkPos = chunkPos; - + protected MCAChunk() { this.dataVersion = -1; } - protected Chunk(MCAWorld world, CompoundTag chunkTag) { - this.world = world; - - CompoundTag levelData = chunkTag.getCompoundTag("Level"); - - chunkPos = new Vector2i( - levelData.getInt("xPos"), - levelData.getInt("zPos") - ); - + protected MCAChunk(CompoundTag chunkTag) { dataVersion = chunkTag.getInt("DataVersion"); } - + + @Override public abstract boolean isGenerated(); - public Vector2i getChunkPos() { - return chunkPos; - } - - public MCAWorld getWorld() { - return world; - } - public int getDataVersion() { return dataVersion; } @@ -79,19 +56,27 @@ public abstract class Chunk { public abstract LightData getLightData(Vector3i pos); - public abstract Biome getBiome(Vector3i pos); + public abstract Biome getBiome(int x, int y, int z); + + public int getMaxY(int x, int z) { + return 255; + } + + public int getMinY(int x, int z) { + return 0; + } - public static Chunk create(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) throws IOException { + public static MCAChunk create(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) throws IOException { int version = chunkTag.getInt("DataVersion"); - if (version < 1400) return new ChunkAnvil112(world, chunkTag, ignoreMissingLightData); - if (version < 2200) return new ChunkAnvil113(world, chunkTag, ignoreMissingLightData); - if (version < 2500) return new ChunkAnvil115(world, chunkTag, ignoreMissingLightData); - return new ChunkAnvil116(world, chunkTag, ignoreMissingLightData); + if (version < 1400) return new ChunkAnvil112(chunkTag, ignoreMissingLightData, world.getBiomeIdMapper(), world.getBlockIdMapper(), world::getForgeBlockIdMapping); + if (version < 2200) return new ChunkAnvil113(chunkTag, ignoreMissingLightData, world.getBiomeIdMapper()); + if (version < 2500) return new ChunkAnvil115(chunkTag, ignoreMissingLightData, world.getBiomeIdMapper()); + return new ChunkAnvil116(chunkTag, ignoreMissingLightData, world.getBiomeIdMapper()); } - - public static Chunk empty(MCAWorld world, Vector2i chunkPos) { - return new EmptyChunk(world, chunkPos); + + public static MCAChunk empty() { + return EmptyChunk.INSTANCE; } - + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCARegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCARegion.java new file mode 100644 index 00000000..0cf3a775 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCARegion.java @@ -0,0 +1,139 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 de.bluecolored.bluemap.core.logger.Logger; +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.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class MCARegion implements Region { + + private final MCAWorld world; + private final File regionFile; + private final Vector2i regionPos; + + public MCARegion(MCAWorld world, File regionFile) throws IllegalArgumentException { + this.world = world; + this.regionFile = regionFile; + + String[] filenameParts = regionFile.getName().split("\\."); + int rX = Integer.parseInt(filenameParts[1]); + int rZ = Integer.parseInt(filenameParts[2]); + + this.regionPos = new Vector2i(rX, rZ); + } + + @Override + public MCAChunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException { + if (!regionFile.exists() || regionFile.length() == 0) return MCAChunk.empty(); + + try (RandomAccessFile raf = new RandomAccessFile(regionFile, "r")) { + + int xzChunk = Math.floorMod(chunkZ, 32) * 32 + Math.floorMod(chunkX, 32); + + raf.seek(xzChunk * 4); + 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 MCAChunk.empty(); + } + + raf.seek(offset + 4); // +4 skip chunk size + + byte compressionTypeByte = raf.readByte(); + CompressionType compressionType = 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, ignoreMissingLightData); + if (!chunk.isGenerated()) return MCAChunk.empty(); + 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 listChunks(long modifiedSince) { + if (!regionFile.exists() || regionFile.length() == 0) return Collections.emptyList(); + + List chunks = new ArrayList<>(1024); //1024 = 32 x 32 chunks per region-file + + try (RandomAccessFile raf = new RandomAccessFile(regionFile, "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.getAbsolutePath() + " (" + ex.toString() + ")"); + } + + return chunks; + } + + @Override + public File getRegionFile() { + return regionFile; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java index 6f212db0..076063e1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -28,10 +28,9 @@ 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 com.google.common.collect.Multimap; -import com.google.common.collect.MultimapBuilder; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.extensions.*; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; @@ -42,43 +41,43 @@ import net.querz.nbt.CompoundTag; import net.querz.nbt.ListTag; import net.querz.nbt.NBTUtil; import net.querz.nbt.Tag; -import net.querz.nbt.mca.CompressionType; -import net.querz.nbt.mca.MCAUtil; -import java.io.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; import java.nio.file.Path; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; public class MCAWorld implements World { - private final UUID uuid; - private final Path worldFolder; - private final MinecraftVersion minecraftVersion; - private String name; - private int seaLevel; - private Vector3i spawnPoint; + private static final Grid CHUNK_GRID = new Grid(16); + private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID); + + @DebugDump private final UUID uuid; + @DebugDump private final Path worldFolder; + private final MinecraftVersion minecraftVersion; + @DebugDump private String name; + @DebugDump private Vector3i spawnPoint; + + private final LoadingCache regionCache; + private final LoadingCache chunkCache; - private final LoadingCache chunkCache; - private BlockIdMapper blockIdMapper; private BlockPropertiesMapper blockPropertiesMapper; private BiomeMapper biomeMapper; - private final Multimap blockStateExtensions; + private final Map> blockStateExtensions; + + @DebugDump private boolean ignoreMissingLightData; - private boolean ignoreMissingLightData; - - private Map forgeBlockMappings; + private final Map forgeBlockMappings; private MCAWorld( Path worldFolder, UUID uuid, MinecraftVersion minecraftVersion, - String name, - int worldHeight, - int seaLevel, + String name, Vector3i spawnPoint, BlockIdMapper blockIdMapper, BlockPropertiesMapper blockPropertiesMapper, @@ -89,7 +88,6 @@ public class MCAWorld implements World { this.worldFolder = worldFolder; this.minecraftVersion = minecraftVersion; this.name = name; - this.seaLevel = seaLevel; this.spawnPoint = spawnPoint; this.blockIdMapper = blockIdMapper; @@ -100,7 +98,7 @@ public class MCAWorld implements World { this.forgeBlockMappings = new HashMap<>(); - this.blockStateExtensions = MultimapBuilder.hashKeys().arrayListValues().build(); + this.blockStateExtensions = new HashMap<>(); registerBlockStateExtension(new SnowyExtension(minecraftVersion)); registerBlockStateExtension(new StairShapeExtension()); registerBlockStateExtension(new FireExtension()); @@ -114,191 +112,85 @@ public class MCAWorld implements World { registerBlockStateExtension(new DoublePlantExtension(minecraftVersion)); registerBlockStateExtension(new DoubleChestExtension()); + 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(chunkPos -> this.loadChunkOrEmpty(chunkPos, 2, 1000)); + .executor(BlueMap.THREAD_POOL) + .maximumSize(500) + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(this::loadChunk); } - + public BlockState getBlockState(Vector3i pos) { - Vector2i chunkPos = blockToChunk(pos); - Chunk chunk = getChunk(chunkPos); - return chunk.getBlockState(pos); + return getChunk(blockToChunk(pos)).getBlockState(pos); } @Override - public Biome getBiome(Vector3i pos) { - if (pos.getY() < getMinY()) { - pos = new Vector3i(pos.getX(), getMinY(), pos.getZ()); - } else if (pos.getY() > getMaxY()) { - pos = new Vector3i(pos.getX(), getMaxY(), pos.getZ()); - } - - Vector2i chunkPos = blockToChunk(pos); - Chunk chunk = getChunk(chunkPos); - return chunk.getBiome(pos); + public Biome getBiome(int x, int y, int z) { + return getChunk(x >> 4, z >> 4).getBiome(x, y, z); } @Override public Block getBlock(Vector3i pos) { - if (pos.getY() < getMinY()) { - return new Block(this, BlockState.AIR, LightData.ZERO, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos); - } else if (pos.getY() > getMaxY()) { - return new Block(this, BlockState.AIR, LightData.SKY, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos); - } - - Vector2i chunkPos = blockToChunk(pos); - Chunk chunk = getChunk(chunkPos); + MCAChunk chunk = getChunk(blockToChunk(pos)); BlockState blockState = getExtendedBlockState(chunk, pos); LightData lightData = chunk.getLightData(pos); - Biome biome = chunk.getBiome(pos); + Biome biome = chunk.getBiome(pos.getX(), pos.getY(), pos.getZ()); BlockProperties properties = blockPropertiesMapper.get(blockState); return new Block(this, blockState, lightData, biome, properties, pos); } - private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) { + private BlockState getExtendedBlockState(MCAChunk chunk, Vector3i pos) { BlockState blockState = chunk.getBlockState(pos); if (chunk instanceof ChunkAnvil112) { // only use extensions if old format chunk (1.12) in the new format block-states are saved with extensions - for (BlockStateExtension ext : blockStateExtensions.get(blockState.getFullId())) { + for (BlockStateExtension ext : blockStateExtensions.getOrDefault(blockState.getFullId(), Collections.emptyList())) { blockState = ext.extend(this, pos, blockState); } } return blockState; } - - public Chunk getChunk(Vector2i chunkPos) { - try { - Chunk chunk = chunkCache.get(chunkPos); - return chunk; - } catch (RuntimeException e) { - if (e.getCause() instanceof InterruptedException) Thread.currentThread().interrupt(); - throw e; - } - } - - private Chunk loadChunkOrEmpty(Vector2i chunkPos, int tries, long tryInterval) { - Exception loadException = null; - for (int i = 0; i < tries; i++) { - try { - return loadChunk(chunkPos); - } catch (IOException | RuntimeException e) { - if (loadException != null) e.addSuppressed(loadException); - loadException = e; - - if (tryInterval > 0 && i+1 < tries) { - try { - Thread.sleep(tryInterval); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - break; - } - } - } - } - Logger.global.logDebug("Unexpected exception trying to load chunk (" + chunkPos + "):" + loadException); - return Chunk.empty(this, chunkPos); - } - - private Chunk loadChunk(Vector2i chunkPos) throws IOException { - Vector2i regionPos = chunkToRegion(chunkPos); - Path regionPath = getMCAFilePath(regionPos); - - File regionFile = regionPath.toFile(); - if (!regionFile.exists() || regionFile.length() <= 0) return Chunk.empty(this, chunkPos); - - try (RandomAccessFile raf = new RandomAccessFile(regionFile, "r")) { - - int xzChunk = Math.floorMod(chunkPos.getY(), 32) * 32 + Math.floorMod(chunkPos.getX(), 32); - - raf.seek(xzChunk * 4); - 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 Chunk.empty(this, chunkPos); - } - - raf.seek(offset + 4); // +4 skip chunk size - - byte compressionTypeByte = raf.readByte(); - CompressionType compressionType = 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) { - return Chunk.create(this, (CompoundTag) tag, ignoreMissingLightData); - } else { - throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName())); - } - - } catch (RuntimeException e) { - throw new IOException(e); - } - } - @Override - public boolean isChunkGenerated(Vector2i chunkPos) { - Chunk chunk = getChunk(chunkPos); - return chunk.isGenerated(); + public MCAChunk getChunk(int x, int z) { + return getChunk(new Vector2i(x, z)); } - + + public MCAChunk getChunk(Vector2i pos) { + return chunkCache.get(pos); + } + @Override - public Collection getChunkList(long modifiedSinceMillis, Predicate filter){ - List chunks = new ArrayList<>(10000); - - if (!getRegionFolder().toFile().isDirectory()) return Collections.emptyList(); - - for (File file : getRegionFolder().toFile().listFiles()) { + public MCARegion getRegion(int x, int z) { + return regionCache.get(new Vector2i(x, z)); + } + + @Override + public Collection listRegions() { + File[] regionFiles = getRegionFolder().toFile().listFiles(); + if (regionFiles == null) return Collections.emptyList(); + + List regions = new ArrayList<>(regionFiles.length); + + for (File file : regionFiles) { if (!file.getName().endsWith(".mca")) continue; if (file.length() <= 0) continue; - - try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { + try { String[] filenameParts = file.getName().split("\\."); int rX = Integer.parseInt(filenameParts[1]); int rZ = Integer.parseInt(filenameParts[2]); - - for (int x = 0; x < 32; x++) { - for (int z = 0; z < 32; z++) { - Vector2i chunk = new Vector2i(rX * 32 + x, rZ * 32 + z); - if (filter.test(chunk)) { - - 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 >= (modifiedSinceMillis / 1000)) { - chunks.add(chunk); - } - - } - } - } - } catch (RuntimeException | IOException ex) { - Logger.global.logWarning("Failed to read .mca file: " + file.getAbsolutePath() + " (" + ex.toString() + ")"); - } + + regions.add(new Vector2i(rX, rZ)); + } catch (NumberFormatException ignore) {} } - - return chunks; + + return regions; } @Override @@ -318,7 +210,27 @@ public class MCAWorld implements World { @Override public int getSeaLevel() { - return seaLevel; + return 63; + } + + @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 @@ -332,8 +244,8 @@ public class MCAWorld implements World { } @Override - public void invalidateChunkCache(Vector2i chunk) { - chunkCache.invalidate(chunk); + public void invalidateChunkCache(int x, int z) { + chunkCache.invalidate(new Vector2i(x, z)); } @Override @@ -381,16 +293,57 @@ public class MCAWorld implements World { return worldFolder.resolve("region"); } - private Path getMCAFilePath(Vector2i region) { - return getRegionFolder().resolve(MCAUtil.createNameFromRegionLocation(region.getX(), region.getY())); + private File getMCAFile(int regionX, int regionZ) { + return getRegionFolder().resolve("r." + regionX + "." + regionZ + ".mca").toFile(); } private void registerBlockStateExtension(BlockStateExtension extension) { for (String id : extension.getAffectedBlockIds()) { - this.blockStateExtensions.put(id, extension); + this.blockStateExtensions.computeIfAbsent(id, t -> new ArrayList<>()).add(extension); } } - + + private MCARegion loadRegion(Vector2i regionPos) { + return loadRegion(regionPos.getX(), regionPos.getY()); + } + + private MCARegion loadRegion(int x, int z) { + File regionPath = getMCAFile(x, z); + return new MCARegion(this, regionPath); + } + + private MCAChunk loadChunk(Vector2i chunkPos) { + return loadChunk(chunkPos.getX(), chunkPos.getY()); + } + + private MCAChunk 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 MCAChunk.empty(); + } + public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion version, BlockIdMapper blockIdMapper, BlockPropertiesMapper blockPropertiesMapper, BiomeMapper biomeIdMapper) throws IOException { return load(worldFolder, uuid, version, blockIdMapper, blockPropertiesMapper, biomeIdMapper, null, false); } @@ -422,9 +375,7 @@ public class MCAWorld implements World { if (name == null) { name = levelData.getString("LevelName") + subDimensionName; } - - int worldHeight = 255; - int seaLevel = 63; + Vector3i spawnPoint = new Vector3i( levelData.getInt("SpawnX"), levelData.getInt("SpawnY"), @@ -435,9 +386,7 @@ public class MCAWorld implements World { worldFolder, uuid, version, - name, - worldHeight, - seaLevel, + name, spawnPoint, blockIdMapper, blockPropertiesMapper, @@ -469,22 +418,8 @@ public class MCAWorld implements World { public static Vector2i blockToChunk(Vector3i pos) { return new Vector2i( - MCAUtil.blockToChunk(pos.getX()), - MCAUtil.blockToChunk(pos.getZ()) - ); - } - - public static Vector2i blockToRegion(Vector3i pos) { - return new Vector2i( - MCAUtil.blockToRegion(pos.getX()), - MCAUtil.blockToRegion(pos.getZ()) - ); - } - - public static Vector2i chunkToRegion(Vector2i pos) { - return new Vector2i( - MCAUtil.chunkToRegion(pos.getX()), - MCAUtil.chunkToRegion(pos.getY()) + pos.getX() >> 4, + pos.getZ() >> 4 ); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoorExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoorExtension.java index 07816694..179aa39f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoorExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoorExtension.java @@ -24,26 +24,24 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; - import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.BlockState; -public class DoorExtension implements BlockStateExtension { +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +public class DoorExtension implements BlockStateExtension { private final Set affectedBlockIds; public DoorExtension(MinecraftVersion version) { - switch (version) { - case MC_1_12: - affectedBlockIds = Sets.newHashSet( + if (version.isBefore(MinecraftVersion.THE_FLATTENING)) { + affectedBlockIds = new HashSet<>(Arrays.asList( "minecraft:wooden_door", "minecraft:iron_door", "minecraft:spruce_door", @@ -51,10 +49,9 @@ public class DoorExtension implements BlockStateExtension { "minecraft:jungle_door", "minecraft:acacia_door", "minecraft:dark_oak_door" - ); - break; - default: - affectedBlockIds = Sets.newHashSet( + )); + } else { + affectedBlockIds = new HashSet<>(Arrays.asList( "minecraft:oak_door", "minecraft:iron_door", "minecraft:spruce_door", @@ -62,8 +59,7 @@ public class DoorExtension implements BlockStateExtension { "minecraft:jungle_door", "minecraft:acacia_door", "minecraft:dark_oak_door" - ); - break; + )); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoubleChestExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoubleChestExtension.java index abe416e6..09e1f51b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoubleChestExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoubleChestExtension.java @@ -24,21 +24,21 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.Set; - import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class DoubleChestExtension implements BlockStateExtension { - private static final Set AFFECTED_BLOCK_IDS = Sets.newHashSet( + private static final Set AFFECTED_BLOCK_IDS = new HashSet<>(Arrays.asList( "minecraft:chest", "minecraft:trapped_chest" - ); + )); @Override public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoublePlantExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoublePlantExtension.java index 47e286fd..7fae0f51 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoublePlantExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoublePlantExtension.java @@ -24,38 +24,31 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.Objects; -import java.util.Set; - import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.*; + public class DoublePlantExtension implements BlockStateExtension { - private final Set affectedBlockIds; public DoublePlantExtension(MinecraftVersion version) { - switch (version) { - case MC_1_12: - affectedBlockIds = Sets.newHashSet( + if (version.isBefore(MinecraftVersion.THE_FLATTENING)) { + affectedBlockIds = new HashSet<>(Collections.singletonList( "minecraft:double_plant" - ); - break; - default: - affectedBlockIds = Sets.newHashSet( + )); + } else { + affectedBlockIds = new HashSet<>(Arrays.asList( "minecraft:sunflower", "minecraft:lilac", "minecraft:tall_grass", "minecraft:large_fern", "minecraft:rose_bush", "minecraft:peony" - ); - break; + )); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/FireExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/FireExtension.java index b78ea99d..17c7fa0f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/FireExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/FireExtension.java @@ -24,20 +24,20 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.Set; - import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + public class FireExtension implements BlockStateExtension { - private static final Set AFFECTED_BLOCK_IDS = Sets.newHashSet( + private static final Set AFFECTED_BLOCK_IDS = new HashSet<>(Collections.singletonList( "minecraft:fire" - ); + )); @Override public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/GlassPaneConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/GlassPaneConnectExtension.java index a8bb94b1..352ef0a5 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/GlassPaneConnectExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/GlassPaneConnectExtension.java @@ -24,14 +24,13 @@ */ package de.bluecolored.bluemap.core.mca.extensions; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import com.google.common.collect.Sets; - public class GlassPaneConnectExtension extends ConnectSameOrFullBlockExtension { - private static final HashSet AFFECTED_BLOCK_IDS = Sets.newHashSet( + private static final HashSet AFFECTED_BLOCK_IDS = new HashSet<>(Arrays.asList( "minecraft:glass_pane", "minecraft:white_stained_glass_pane", "minecraft:orange_stained_glass_pane", @@ -49,7 +48,7 @@ public class GlassPaneConnectExtension extends ConnectSameOrFullBlockExtension { "minecraft:red_stained_glass_pane", "minecraft:black_stained_glass_pane", "minecraft:iron_bars" - ); + )); @Override public Set getAffectedBlockIds() { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/NetherFenceConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/NetherFenceConnectExtension.java index 39ff3760..dfe3f0bd 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/NetherFenceConnectExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/NetherFenceConnectExtension.java @@ -24,16 +24,16 @@ */ package de.bluecolored.bluemap.core.mca.extensions; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Set; -import com.google.common.collect.Sets; - public class NetherFenceConnectExtension extends ConnectSameOrFullBlockExtension { - private static final HashSet AFFECTED_BLOCK_IDS = Sets.newHashSet( + private static final HashSet AFFECTED_BLOCK_IDS = new HashSet<>(Collections.singletonList( "minecraft:nether_brick_fence" - ); + )); @Override public Set getAffectedBlockIds() { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/RedstoneExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/RedstoneExtension.java index d6148332..33c43ac1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/RedstoneExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/RedstoneExtension.java @@ -24,23 +24,24 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.Set; - import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + public class RedstoneExtension implements BlockStateExtension { - private static final Set AFFECTED_BLOCK_IDS = Sets.newHashSet( + private static final Set AFFECTED_BLOCK_IDS = new HashSet<>(Collections.singletonList( "minecraft:redstone_wire" - ); + )); - private static final Set CONNECTIBLE = Sets.newHashSet( + private static final Set CONNECTIBLE = new HashSet<>(Arrays.asList( "minecraft:redstone_wire", "minecraft:redstone_wall_torch", "minecraft:redstone_torch", @@ -52,7 +53,7 @@ public class RedstoneExtension implements BlockStateExtension { "minecraft:oak_pressure_plate", "minecraft:light_weighted_pressure_plate", "minecraft:heavy_weighted_pressure_plate" - ); + )); @Override public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/SnowyExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/SnowyExtension.java index c3ed64fc..b03cc95f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/SnowyExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/SnowyExtension.java @@ -24,15 +24,15 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.Set; - import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class SnowyExtension implements BlockStateExtension { private final Set affectedBlockIds; @@ -41,23 +41,20 @@ public class SnowyExtension implements BlockStateExtension { private final String snowBlockId; public SnowyExtension(MinecraftVersion version) { - switch (version) { - case MC_1_12: - affectedBlockIds = Sets.newHashSet( + if (version.isBefore(MinecraftVersion.THE_FLATTENING)) { + affectedBlockIds = new HashSet<>(Arrays.asList( "minecraft:grass", "minecraft:mycelium" - ); - snowLayerId = "minecraft:snow_layer"; - snowBlockId = "minecraft:snow"; - break; - default: - affectedBlockIds = Sets.newHashSet( - "minecraft:grass_block", - "minecraft:podzol" - ); - snowLayerId = "minecraft:snow"; - snowBlockId = "minecraft:snow_block"; - break; + )); + snowLayerId = "minecraft:snow_layer"; + snowBlockId = "minecraft:snow"; + } else { + affectedBlockIds = new HashSet<>(Arrays.asList( + "minecraft:grass_block", + "minecraft:podzol" + )); + snowLayerId = "minecraft:snow"; + snowBlockId = "minecraft:snow_block"; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/StairShapeExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/StairShapeExtension.java index dd4bf034..38782bd5 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/StairShapeExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/StairShapeExtension.java @@ -24,18 +24,18 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.Set; - import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class StairShapeExtension implements BlockStateExtension { - private static final Set AFFECTED_BLOCK_IDS = Sets.newHashSet( + private static final Set AFFECTED_BLOCK_IDS = new HashSet<>(Arrays.asList( "minecraft:oak_stairs", "minecraft:cobblestone_stairs", "minecraft:brick_stairs", @@ -50,7 +50,7 @@ public class StairShapeExtension implements BlockStateExtension { "minecraft:dark_oak_stairs", "minecraft:red_sandstone_stairs", "minecraft:purpur_stairs" - ); + )); @Override public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/TripwireConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/TripwireConnectExtension.java index 7c953b41..fbec3254 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/TripwireConnectExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/TripwireConnectExtension.java @@ -24,16 +24,15 @@ */ package de.bluecolored.bluemap.core.mca.extensions; +import java.util.Collections; import java.util.HashSet; import java.util.Set; -import com.google.common.collect.Sets; - public class TripwireConnectExtension extends ConnectExtension { - private static final HashSet AFFECTED_BLOCK_IDS = Sets.newHashSet( + private static final HashSet AFFECTED_BLOCK_IDS = new HashSet<>(Collections.singletonList( "minecraft:tripwire" - ); + )); @Override public Set getAffectedBlockIds() { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WallConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WallConnectExtension.java index 44526c9a..ff0f0148 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WallConnectExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WallConnectExtension.java @@ -24,23 +24,22 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - import com.flowpowered.math.vector.Vector3i; -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + public class WallConnectExtension extends ConnectSameOrFullBlockExtension { - private static final HashSet AFFECTED_BLOCK_IDS = Sets.newHashSet( + private static final HashSet AFFECTED_BLOCK_IDS = new HashSet<>(Arrays.asList( "minecraft:cobblestone_wall", "minecraft:mossy_cobblestone_wall" - ); + )); @Override public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WoodenFenceConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WoodenFenceConnectExtension.java index 03fdb519..e70ac9d1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WoodenFenceConnectExtension.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WoodenFenceConnectExtension.java @@ -24,38 +24,35 @@ */ package de.bluecolored.bluemap.core.mca.extensions; -import java.util.Set; - -import com.google.common.collect.Sets; - import de.bluecolored.bluemap.core.MinecraftVersion; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class WoodenFenceConnectExtension extends ConnectSameOrFullBlockExtension { private final Set affectedBlockIds; public WoodenFenceConnectExtension(MinecraftVersion version) { - switch (version) { - case MC_1_12: - affectedBlockIds = Sets.newHashSet( + if (version.isBefore(MinecraftVersion.THE_FLATTENING)) { + affectedBlockIds = new HashSet<>(Arrays.asList( "minecraft:fence", "minecraft:spruce_fence", "minecraft:birch_fence", "minecraft:jungle_fence", "minecraft:dark_oak_fence", "minecraft:acacia_fence" - ); - break; - default: - affectedBlockIds = Sets.newHashSet( + )); + } else { + affectedBlockIds = new HashSet<>(Arrays.asList( "minecraft:oak_fence", "minecraft:spruce_fence", "minecraft:birch_fence", "minecraft:jungle_fence", "minecraft:dark_oak_fence", "minecraft:acacia_fence" - ); - break; + )); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/metrics/Metrics.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/metrics/Metrics.java index 556f2539..450bc132 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/metrics/Metrics.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/metrics/Metrics.java @@ -24,6 +24,11 @@ */ package de.bluecolored.bluemap.core.metrics; +import com.google.gson.JsonObject; +import de.bluecolored.bluemap.core.BlueMap; +import de.bluecolored.bluemap.core.logger.Logger; + +import javax.net.ssl.HttpsURLConnection; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -32,13 +37,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; -import javax.net.ssl.HttpsURLConnection; - -import com.google.gson.JsonObject; - -import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.logger.Logger; - public class Metrics { private static final String METRICS_REPORT_URL = "https://metrics.bluecolored.de/bluemap/"; @@ -81,7 +79,7 @@ public class Metrics { StringBuilder builder = new StringBuilder(); while ((line = in.readLine()) != null) { - builder.append(line + "\n"); + builder.append(line).append("\n"); } return builder.toString(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Face.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Face.java index 7366e357..3d2f2001 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Face.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Face.java @@ -28,22 +28,21 @@ import com.flowpowered.math.imaginary.Quaternionf; import com.flowpowered.math.matrix.Matrix3f; import com.flowpowered.math.vector.Vector2f; import com.flowpowered.math.vector.Vector3f; - import de.bluecolored.bluemap.core.util.MathUtils; public class Face { - private Vector3f p1, p2, p3; // points - private Vector3f n1, n2, n3; // normals + private final VectorM3f p1, p2, p3; // points + private final VectorM3f n1, n2, n3; // normals private Vector3f c1, c2, c3; // vertex-colors private Vector2f uv1, uv2, uv3; // texture UV private int materialIndex; private boolean normalizedNormals; public Face(Vector3f p1, Vector3f p2, Vector3f p3, Vector2f uv1, Vector2f uv2, Vector2f uv3, int materialIndex) { - this.p1 = p1; - this.p2 = p2; - this.p3 = p3; + this.p1 = new VectorM3f(p1); + this.p2 = new VectorM3f(p2); + this.p3 = new VectorM3f(p3); this.uv1 = uv1; this.uv2 = uv2; @@ -51,10 +50,9 @@ public class Face { this.materialIndex = materialIndex; - Vector3f faceNormal = getFaceNormal(); - this.n1 = faceNormal; - this.n2 = faceNormal; - this.n3 = faceNormal; + this.n1 = getFaceNormal(); + this.n2 = new VectorM3f(n1); + this.n3 = new VectorM3f(n1); this.normalizedNormals = true; Vector3f color = Vector3f.ONE; @@ -64,84 +62,86 @@ public class Face { } public void rotate(Quaternionf rotation) { - p1 = rotation.rotate(p1); - p2 = rotation.rotate(p2); - p3 = rotation.rotate(p3); + p1.rotate(rotation); + p2.rotate(rotation); + p3.rotate(rotation); - n1 = rotation.rotate(n1); - n2 = rotation.rotate(n2); - n3 = rotation.rotate(n3); + n1.rotate(rotation); + n2.rotate(rotation); + n3.rotate(rotation); } public void transform(Matrix3f transformation) { - p1 = transformation.transform(p1); - p2 = transformation.transform(p2); - p3 = transformation.transform(p3); + MatrixM3f mtransform = new MatrixM3f(transformation); - n1 = transformation.transform(n1); - n2 = transformation.transform(n2); - n3 = transformation.transform(n3); + p1.transform(mtransform); + p2.transform(mtransform); + p3.transform(mtransform); + + n1.transform(mtransform); + n2.transform(mtransform); + n3.transform(mtransform); normalizedNormals = false; } public void translate(Vector3f translation) { - p1 = translation.add(p1); - p2 = translation.add(p2); - p3 = translation.add(p3); + p1.add(translation); + p2.add(translation); + p3.add(translation); } public Vector3f getP1() { - return p1; + return p1.toVector3f(); } public void setP1(Vector3f p1) { - this.p1 = p1; + this.p1.set(p1); } public Vector3f getP2() { - return p2; + return p2.toVector3f(); } public void setP2(Vector3f p2) { - this.p2 = p2; + this.p2.set(p2); } public Vector3f getP3() { - return p3; + return p3.toVector3f(); } public void setP3(Vector3f p3) { - this.p3 = p3; + this.p3.set(p3); } public Vector3f getN1() { - normlizeNormals(); - return n1; + normalizeNormals(); + return n1.toVector3f(); } public void setN1(Vector3f n1) { - this.n1 = n1; + this.n1.set(n1); normalizedNormals = false; } public Vector3f getN2() { - normlizeNormals(); - return n2; + normalizeNormals(); + return n2.toVector3f(); } public void setN2(Vector3f n2) { - this.n2 = n2; + this.n2.set(n2); normalizedNormals = false; } public Vector3f getN3() { - normlizeNormals(); - return n3; + normalizeNormals(); + return n3.toVector3f(); } public void setN3(Vector3f n3) { - this.n3 = n3; + this.n3.set(n3); normalizedNormals = false; } @@ -201,16 +201,16 @@ public class Face { this.materialIndex = materialIndex; } - public Vector3f getFaceNormal() { + private VectorM3f getFaceNormal() { return MathUtils.getSurfaceNormal(p1, p2, p3); } - private void normlizeNormals() { + private void normalizeNormals() { if (normalizedNormals) return; - n1 = n1.normalize(); - n2 = n2.normalize(); - n3 = n3.normalize(); + n1.normalize(); + n2.normalize(); + n3.normalize(); normalizedNormals = true; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/MatrixM3f.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/MatrixM3f.java new file mode 100644 index 00000000..41b91f17 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/MatrixM3f.java @@ -0,0 +1,47 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.model; + +import com.flowpowered.math.matrix.Matrix3f; + +public class MatrixM3f { + + public float m00, m01, m02; + public float m10, m11, m12; + public float m20, m21, m22; + + public MatrixM3f(Matrix3f v) { + this.m00 = v.get(0, 0); + this.m01 = v.get(0, 1); + this.m02 = v.get(0, 2); + this.m10 = v.get(1, 0); + this.m11 = v.get(1, 1); + this.m12 = v.get(1, 2); + this.m20 = v.get(2, 0); + this.m21 = v.get(2, 1); + this.m22 = v.get(2, 2); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/VectorM3f.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/VectorM3f.java new file mode 100644 index 00000000..72911a92 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/VectorM3f.java @@ -0,0 +1,120 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.model; + +import com.flowpowered.math.GenericMath; +import com.flowpowered.math.imaginary.Quaternionf; +import com.flowpowered.math.vector.Vector3f; + +public class VectorM3f { + + public float x, y, z; + + public VectorM3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public VectorM3f(VectorM3f v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + } + + public VectorM3f(Vector3f v) { + this.x = v.getX(); + this.y = v.getY(); + this.z = v.getZ(); + } + + public void set(Vector3f v) { + this.x = v.getX(); + this.y = v.getY(); + this.z = v.getZ(); + } + + public void add(VectorM3f translation) { + this.x += translation.x; + this.y += translation.y; + this.z += translation.z; + } + + public void add(Vector3f translation) { + this.x += translation.getX(); + this.y += translation.getY(); + this.z += translation.getZ(); + } + + public void rotate(Quaternionf rotation) { + final float length = rotation.length(); + if (Math.abs(length) < GenericMath.FLT_EPSILON) { + throw new ArithmeticException("Cannot rotate by the zero quaternion"); + } + final float nx = rotation.getX() / length; + final float ny = rotation.getY() / length; + final float nz = rotation.getZ() / length; + final float nw = rotation.getW() / length; + final float px = nw * x + ny * z - nz * y; + final float py = nw * y + nz * x - nx * z; + final float pz = nw * z + nx * y - ny * x; + final float pw = -nx * x - ny * y - nz * z; + + this.x = pw * -nx + px * nw - py * nz + pz * ny; + this.y = pw * -ny + py * nw - pz * nx + px * nz; + this.z = pw * -nz + pz * nw - px * ny + py * nx; + } + + public void transform(MatrixM3f t) { + float lx = x, ly = y; + this.x = t.m00 * lx + t.m01 * ly + t.m02 * z; + this.y = t.m10 * lx + t.m11 * ly + t.m12 * z; + this.z = t.m20 * lx + t.m21 * ly + t.m22 * z; + } + + public void normalize() { + final float length = length(); + if (Math.abs(length) < GenericMath.FLT_EPSILON) { + throw new ArithmeticException("Cannot normalize the zero vector"); + } + + x /= length; + y /= length; + z /= length; + } + + public float length() { + return (float) Math.sqrt(lengthSquared()); + } + + public float lengthSquared() { + return x * x + y * y + z * z; + } + + public Vector3f toVector3f() { + return new Vector3f(this.x, this.y, this.z); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java deleted file mode 100644 index 4e876042..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.render; - -import de.bluecolored.bluemap.core.render.hires.HiresModel; -import de.bluecolored.bluemap.core.render.hires.HiresModelManager; -import de.bluecolored.bluemap.core.render.lowres.LowresModelManager; -import de.bluecolored.bluemap.core.util.AABB; - -public class TileRenderer { - private HiresModelManager hiresModelManager; - private LowresModelManager lowresModelManager; - - public TileRenderer(HiresModelManager hiresModelManager, LowresModelManager lowresModelManager) { - this.hiresModelManager = hiresModelManager; - this.lowresModelManager = lowresModelManager; - } - - /** - * Renders the provided WorldTile (only) if the world is generated - */ - public void render(WorldTile tile) { - //check if the region is generated before rendering, don't render if it's not generated - AABB area = hiresModelManager.getTileRegion(tile); - if (!tile.getWorld().isAreaGenerated(area)) return; - - HiresModel hiresModel = hiresModelManager.render(tile); - lowresModelManager.render(hiresModel); - } - - /** - * Saves changes to disk - */ - public void save(){ - lowresModelManager.save(); - } - - public HiresModelManager getHiresModelManager() { - return hiresModelManager; - } - - public LowresModelManager getLowresModelManager() { - return lowresModelManager; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java index 256a9718..2b752b0c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java @@ -24,34 +24,34 @@ */ package de.bluecolored.bluemap.core.resourcepack; -import java.awt.Color; +import com.flowpowered.math.GenericMath; +import com.flowpowered.math.vector.Vector2f; +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3f; +import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.util.ConfigUtils; +import de.bluecolored.bluemap.core.util.MathUtils; +import de.bluecolored.bluemap.core.world.Biome; +import de.bluecolored.bluemap.core.world.Block; +import de.bluecolored.bluemap.core.world.World; +import org.spongepowered.configurate.ConfigurationNode; + +import java.awt.*; import java.awt.image.BufferedImage; -import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.function.Function; -import com.flowpowered.math.GenericMath; -import com.flowpowered.math.vector.Vector2f; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3f; -import com.flowpowered.math.vector.Vector3i; - -import de.bluecolored.bluemap.core.util.ConfigUtils; -import de.bluecolored.bluemap.core.util.MathUtils; -import de.bluecolored.bluemap.core.world.Biome; -import de.bluecolored.bluemap.core.world.Block; -import de.bluecolored.bluemap.core.world.World; -import ninja.leaping.configurate.ConfigurationNode; - +@DebugDump public class BlockColorCalculator { private BufferedImage foliageMap; private BufferedImage grassMap; - private Map> blockColorMap; + private final Map> blockColorMap; public BlockColorCalculator(BufferedImage foliageMap, BufferedImage grassMap) { this.foliageMap = foliageMap; @@ -60,12 +60,12 @@ public class BlockColorCalculator { this.blockColorMap = new HashMap<>(); } - public void loadColorConfig(ConfigurationNode colorConfig) throws IOException { + public void loadColorConfig(ConfigurationNode colorConfig) { blockColorMap.clear(); - for (Entry entry : colorConfig.getChildrenMap().entrySet()){ + for (Entry entry : colorConfig.childrenMap().entrySet()){ String key = entry.getKey().toString(); - String value = entry.getValue().getString(); + String value = entry.getValue().getString(""); Function colorFunction; switch (value) { @@ -200,7 +200,7 @@ public class BlockColorCalculator { z++; } - return world.getBiome(new Vector3i(x, y, z)); + return world.getBiome(x, y, z); } }; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java index 31ce1e52..cdde5c7f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java @@ -30,8 +30,8 @@ import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Face; import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; import de.bluecolored.bluemap.core.util.Axis; import de.bluecolored.bluemap.core.util.Direction; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; import java.io.*; import java.nio.charset.StandardCharsets; @@ -250,22 +250,22 @@ public class BlockModelResource { InputStream fileIn = sourcesAccess.readFile(modelPath); ConfigurationNode config = GsonConfigurationLoader.builder() - .setSource(() -> new BufferedReader(new InputStreamReader(fileIn, StandardCharsets.UTF_8))) + .source(() -> new BufferedReader(new InputStreamReader(fileIn, StandardCharsets.UTF_8))) .build() .load(); - for (Entry entry : config.getNode("textures").getChildrenMap().entrySet()) { + for (Entry entry : config.node("textures").childrenMap().entrySet()) { if (entry.getKey().equals(JSON_COMMENT)) continue; String key = entry.getKey().toString(); - String value = entry.getValue().getString(null); + String value = entry.getValue().getString(); if (("#" + key).equals(value)) continue; // skip direct loop textures.putIfAbsent(key, value); } - String parentPath = config.getNode("parent").getString(); + String parentPath = config.node("parent").getString(); if (parentPath != null) { if (parentPath.startsWith("builtin")) { switch (parentPath) { @@ -276,7 +276,7 @@ public class BlockModelResource { } else { try { parentPath = ResourcePack.namespacedToAbsoluteResourcePath(parentPath, "models") + ".json"; - blockModel = this.buildNoReset(parentPath, renderElements && config.getNode("elements").isVirtual(), topModelPath); + blockModel = this.buildNoReset(parentPath, renderElements && config.node("elements").virtual(), topModelPath); } catch (IOException ex) { throw new ParseResourceException("Failed to load parent model " + parentPath + " of model " + topModelPath, ex); } @@ -284,7 +284,7 @@ public class BlockModelResource { } if (renderElements) { - for (ConfigurationNode elementNode : config.getNode("elements").getChildrenList()) { + for (ConfigurationNode elementNode : config.node("elements").childrenList()) { blockModel.elements.add(buildElement(blockModel, elementNode, topModelPath)); } } @@ -334,24 +334,24 @@ public class BlockModelResource { private Element buildElement(BlockModelResource model, ConfigurationNode node, String topModelPath) throws ParseResourceException { Element element = model.new Element(); - element.from = readVector3f(node.getNode("from")); - element.to = readVector3f(node.getNode("to")); + element.from = readVector3f(node.node("from")); + element.to = readVector3f(node.node("to")); - element.shade = node.getNode("shade").getBoolean(true); + element.shade = node.node("shade").getBoolean(true); boolean fullElement = element.from.equals(FULL_CUBE_FROM) && element.to.equals(FULL_CUBE_TO); - if (!node.getNode("rotation").isVirtual()) { - element.rotation.angle = node.getNode("rotation", "angle").getFloat(0); - element.rotation.axis = Axis.fromString(node.getNode("rotation", "axis").getString("x")); - if (!node.getNode("rotation", "origin").isVirtual()) element.rotation.origin = readVector3f(node.getNode("rotation", "origin")); - element.rotation.rescale = node.getNode("rotation", "rescale").getBoolean(false); + if (!node.node("rotation").virtual()) { + element.rotation.angle = node.node("rotation", "angle").getFloat(0); + element.rotation.axis = Axis.fromString(node.node("rotation", "axis").getString("x")); + if (!node.node("rotation", "origin").virtual()) element.rotation.origin = readVector3f(node.node("rotation", "origin")); + element.rotation.rescale = node.node("rotation", "rescale").getBoolean(false); } boolean allDirs = true; for (Direction direction : Direction.values()) { - ConfigurationNode faceNode = node.getNode("faces", direction.name().toLowerCase()); - if (!faceNode.isVirtual()) { + ConfigurationNode faceNode = node.node("faces", direction.name().toLowerCase()); + if (!faceNode.virtual()) { try { Face face = buildFace(element, direction, faceNode); element.faces.put(direction, face); @@ -372,13 +372,13 @@ public class BlockModelResource { try { Face face = element.new Face(direction); - if (!node.getNode("uv").isVirtual()) face.uv = readVector4f(node.getNode("uv")); - face.texture = getTexture(node.getNode("texture").getString("")); - face.tinted = node.getNode("tintindex").getInt(-1) >= 0; - face.rotation = node.getNode("rotation").getInt(0); + if (!node.node("uv").virtual()) face.uv = readVector4f(node.node("uv")); + face.texture = getTexture(node.node("texture").getString("")); + face.tinted = node.node("tintindex").getInt(-1) >= 0; + face.rotation = node.node("rotation").getInt(0); - if (!node.getNode("cullface").isVirtual()) { - String dirString = node.getNode("cullface").getString(""); + if (!node.node("cullface").virtual()) { + String dirString = node.node("cullface").getString(""); if (dirString.equals("bottom")) dirString = "down"; if (dirString.equals("top")) dirString = "up"; @@ -389,14 +389,14 @@ public class BlockModelResource { return face; } catch (FileNotFoundException ex) { - throw new ParseResourceException("There is no texture with the path: " + node.getNode("texture").getString(), ex); + throw new ParseResourceException("There is no texture with the path: " + node.node("texture").getString(), ex); } catch (NoSuchElementException ex) { - throw new ParseResourceException("Texture key '" + node.getNode("texture").getString() + "' has no texture assigned!", ex); + throw new ParseResourceException("Texture key '" + node.node("texture").getString() + "' has no texture assigned!", ex); } } private Vector3f readVector3f(ConfigurationNode node) throws ParseResourceException { - List nodeList = node.getChildrenList(); + List nodeList = node.childrenList(); if (nodeList.size() < 3) throw new ParseResourceException("Failed to load Vector3: Not enough values in list-node!"); return new Vector3f( @@ -407,7 +407,7 @@ public class BlockModelResource { } private Vector4f readVector4f(ConfigurationNode node) throws ParseResourceException { - List nodeList = node.getChildrenList(); + List nodeList = node.childrenList(); if (nodeList.size() < 4) throw new ParseResourceException("Failed to load Vector4: Not enough values in list-node!"); return new Vector4f( 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 10ff83dd..d1b0e56e 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 @@ -26,13 +26,14 @@ package de.bluecolored.bluemap.core.resourcepack; import com.flowpowered.math.vector.Vector2f; import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.resourcepack.PropertyCondition.All; import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.world.BlockState; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; import org.apache.commons.lang3.StringUtils; import java.io.BufferedReader; @@ -44,9 +45,11 @@ import java.util.*; import java.util.Map.Entry; public class BlockStateResource { - - private List variants = new ArrayList<>(0); - private Collection multipart = new ArrayList<>(0); + + private static final MinecraftVersion NEW_MODEL_PATH_VERSION = new MinecraftVersion(1, 13); + + private final List variants = new ArrayList<>(0); + private final Collection multipart = new ArrayList<>(0); private BlockStateResource() { } @@ -90,7 +93,7 @@ public class BlockStateResource { return models; } - private class Variant { + private static class Variant { private PropertyCondition condition = PropertyCondition.all(); private Collection> models = new ArrayList<>(); @@ -127,8 +130,8 @@ public class BlockStateResource { private static class Weighted { - private T value; - private double weight; + private final T value; + private final double weight; public Weighted(T value, double weight) { this.value = value; @@ -157,18 +160,18 @@ public class BlockStateResource { InputStream fileIn = sourcesAccess.readFile(blockstateFile); ConfigurationNode config = GsonConfigurationLoader.builder() - .setSource(() -> new BufferedReader(new InputStreamReader(fileIn, StandardCharsets.UTF_8))) + .source(() -> new BufferedReader(new InputStreamReader(fileIn, StandardCharsets.UTF_8))) .build() .load(); - if (!config.getNode("forge_marker").isVirtual()) { + if (!config.node("forge_marker").virtual()) { return buildForge(config, blockstateFile); } BlockStateResource blockState = new BlockStateResource(); // create variants - for (Entry entry : config.getNode("variants").getChildrenMap().entrySet()) { + for (Entry entry : config.node("variants").childrenMap().entrySet()) { if (entry.getKey().equals(JSON_COMMENT)) continue; try { @@ -178,7 +181,7 @@ public class BlockStateResource { //some exceptions in 1.12 resource packs that we ignore if (conditionString.equals("all") || conditionString.equals("map")) continue; - Variant variant = blockState.new Variant(); + Variant variant = new Variant(); variant.condition = parseConditionString(conditionString); variant.models = loadModels(transformedModelNode, blockstateFile, null); @@ -192,14 +195,14 @@ public class BlockStateResource { } // create multipart - for (ConfigurationNode partNode : config.getNode("multipart").getChildrenList()) { + for (ConfigurationNode partNode : config.node("multipart").childrenList()) { try { - Variant variant = blockState.new Variant(); - ConfigurationNode whenNode = partNode.getNode("when"); - if (!whenNode.isVirtual()) { + Variant variant = new Variant(); + ConfigurationNode whenNode = partNode.node("when"); + if (!whenNode.virtual()) { variant.condition = parseCondition(whenNode); } - variant.models = loadModels(partNode.getNode("apply"), blockstateFile, null); + variant.models = loadModels(partNode.node("apply"), blockstateFile, null); variant.updateTotalWeight(); variant.checkValid(); @@ -217,7 +220,7 @@ public class BlockStateResource { Collection> models = new ArrayList<>(); if (node.isList()) { - for (ConfigurationNode modelNode : node.getChildrenList()) { + for (ConfigurationNode modelNode : node.childrenList()) { try { models.add(loadModel(modelNode, overrideTextures)); } catch (ParseResourceException ex) { @@ -236,19 +239,16 @@ public class BlockStateResource { } private Weighted loadModel(ConfigurationNode node, Map overrideTextures) throws ParseResourceException { - String namespacedModelPath = node.getNode("model").getString(); + String namespacedModelPath = node.node("model").getString(); if (namespacedModelPath == null) throw new ParseResourceException("No model defined!"); String modelPath; - switch (resourcePack.getMinecraftVersion()) { - case MC_1_12: - modelPath = ResourcePack.namespacedToAbsoluteResourcePath(namespacedModelPath, "models/block") + ".json"; - break; - default: - modelPath = ResourcePack.namespacedToAbsoluteResourcePath(namespacedModelPath, "models") + ".json"; - break; + if (resourcePack.getMinecraftVersion().isBefore(NEW_MODEL_PATH_VERSION)) { + modelPath = ResourcePack.namespacedToAbsoluteResourcePath(namespacedModelPath, "models/block") + ".json"; + }else { + modelPath = ResourcePack.namespacedToAbsoluteResourcePath(namespacedModelPath, "models") + ".json"; } BlockModelResource model = resourcePack.blockModelResources.get(modelPath); @@ -264,33 +264,33 @@ public class BlockStateResource { resourcePack.blockModelResources.put(modelPath, model); } - Vector2f rotation = new Vector2f(node.getNode("x").getFloat(0), node.getNode("y").getFloat(0)); - boolean uvLock = node.getNode("uvlock").getBoolean(false); + Vector2f rotation = new Vector2f(node.node("x").getFloat(0), node.node("y").getFloat(0)); + boolean uvLock = node.node("uvlock").getBoolean(false); TransformedBlockModelResource transformedModel = new TransformedBlockModelResource(rotation, uvLock, model); - return new Weighted(transformedModel, node.getNode("weight").getDouble(1d)); + return new Weighted<>(transformedModel, node.node("weight").getDouble(1d)); } private PropertyCondition parseCondition(ConfigurationNode conditionNode) { List andConditions = new ArrayList<>(); - for (Entry entry : conditionNode.getChildrenMap().entrySet()) { + for (Entry entry : conditionNode.childrenMap().entrySet()) { String key = entry.getKey().toString(); if (key.equals(JSON_COMMENT)) continue; if (key.equals("OR")) { List orConditions = new ArrayList<>(); - for (ConfigurationNode orConditionNode : entry.getValue().getChildrenList()) { + for (ConfigurationNode orConditionNode : entry.getValue().childrenList()) { orConditions.add(parseCondition(orConditionNode)); } andConditions.add( - PropertyCondition.or(orConditions.toArray(new PropertyCondition[orConditions.size()]))); + PropertyCondition.or(orConditions.toArray(new PropertyCondition[0]))); } else { String[] values = StringUtils.split(entry.getValue().getString(""), '|'); andConditions.add(PropertyCondition.property(key, values)); } } - return PropertyCondition.and(andConditions.toArray(new PropertyCondition[andConditions.size()])); + return PropertyCondition.and(andConditions.toArray(new PropertyCondition[0])); } private PropertyCondition parseConditionString(String conditionString) throws IllegalArgumentException { @@ -311,24 +311,24 @@ public class BlockStateResource { } else if (conditions.size() == 1) { condition = conditions.get(0); } else { - condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[conditions.size()])); + condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[0])); } return condition; } private BlockStateResource buildForge(ConfigurationNode config, String blockstateFile) { - ConfigurationNode modelDefaults = config.getNode("defaults"); + ConfigurationNode modelDefaults = config.node("defaults"); List variants = new ArrayList<>(); - for (Entry entry : config.getNode("variants").getChildrenMap().entrySet()) { + for (Entry entry : config.node("variants").childrenMap().entrySet()) { if (entry.getKey().equals(JSON_COMMENT)) continue; if (isForgeStraightVariant(entry.getValue())) continue; // create variants for single property List propertyVariants = new ArrayList<>(); String key = entry.getKey().toString(); - for (Entry value : entry.getValue().getChildrenMap().entrySet()) { + for (Entry value : entry.getValue().childrenMap().entrySet()) { if (value.getKey().equals(JSON_COMMENT)) continue; ForgeVariant variant = new ForgeVariant(); @@ -355,26 +355,26 @@ public class BlockStateResource { //create all possible property-variants BlockStateResource blockState = new BlockStateResource(); for (ForgeVariant forgeVariant : variants) { - Variant variant = blockState.new Variant(); + Variant variant = new Variant(); - ConfigurationNode modelNode = forgeVariant.node.mergeValuesFrom(modelDefaults); + ConfigurationNode modelNode = forgeVariant.node.mergeFrom(modelDefaults); Map textures = new HashMap<>(); - for (Entry entry : modelNode.getNode("textures").getChildrenMap().entrySet()) { + for (Entry entry : modelNode.node("textures").childrenMap().entrySet()) { if (entry.getKey().equals(JSON_COMMENT)) continue; - textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null)); + textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString()); } List conditions = new ArrayList<>(forgeVariant.properties.size()); for (Entry property : forgeVariant.properties.entrySet()) { conditions.add(PropertyCondition.property(property.getKey(), property.getValue())); } - variant.condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[conditions.size()])); + variant.condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[0])); variant.models.addAll(loadModels(modelNode, blockstateFile, textures)); - for (Entry entry : modelNode.getNode("submodel").getChildrenMap().entrySet()) { + for (Entry entry : modelNode.node("submodel").childrenMap().entrySet()) { if (entry.getKey().equals(JSON_COMMENT)) continue; variant.models.addAll(loadModels(entry.getValue(), blockstateFile, textures)); @@ -392,22 +392,22 @@ public class BlockStateResource { } //create default straight variant - ConfigurationNode normalNode = config.getNode("variants", "normal"); - if (normalNode.isVirtual() || isForgeStraightVariant(normalNode)) { - normalNode.mergeValuesFrom(modelDefaults); + ConfigurationNode normalNode = config.node("variants", "normal"); + if (normalNode.virtual() || isForgeStraightVariant(normalNode)) { + normalNode.mergeFrom(modelDefaults); Map textures = new HashMap<>(); - for (Entry entry : normalNode.getNode("textures").getChildrenMap().entrySet()) { + for (Entry entry : normalNode.node("textures").childrenMap().entrySet()) { if (entry.getKey().equals(JSON_COMMENT)) continue; - textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null)); + textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString()); } - Variant variant = blockState.new Variant(); + Variant variant = new Variant(); variant.condition = PropertyCondition.all(); variant.models.addAll(loadModels(normalNode, blockstateFile, textures)); - for (Entry entry : normalNode.getNode("submodel").getChildrenMap().entrySet()) { + for (Entry entry : normalNode.node("submodel").childrenMap().entrySet()) { if (entry.getKey().equals(JSON_COMMENT)) continue; variant.models.addAll(loadModels(entry.getValue(), blockstateFile, textures)); @@ -431,7 +431,7 @@ public class BlockStateResource { if (node.isList()) return true; - for (Entry entry : node.getChildrenMap().entrySet()) { + for (Entry entry : node.childrenMap().entrySet()) { if (entry.getKey().equals(JSON_COMMENT)) continue; if (!entry.getValue().isMap()) return true; } @@ -441,7 +441,7 @@ public class BlockStateResource { private static class ForgeVariant { public Map properties = new HashMap<>(); - public ConfigurationNode node = GsonConfigurationLoader.builder().build().createEmptyNode(); + public ConfigurationNode node = GsonConfigurationLoader.builder().build().createNode(); public ForgeVariant createMerge(ForgeVariant other) { ForgeVariant merge = new ForgeVariant(); @@ -449,8 +449,8 @@ public class BlockStateResource { merge.properties.putAll(this.properties); merge.properties.putAll(other.properties); - merge.node.mergeValuesFrom(this.node); - merge.node.mergeValuesFrom(other.node); + merge.node.mergeFrom(this.node); + merge.node.mergeFrom(other.node); return merge; } @@ -463,6 +463,8 @@ public class BlockStateResource { if (errorMessage == null) errorMessage = throwable.toString(); Logger.global.logDebug(" > " + errorMessage); + //Logger.global.logError("DETAIL: ", throwable); + throwable = throwable.getCause(); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/PropertyCondition.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/PropertyCondition.java index 7923b743..cc8ad49d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/PropertyCondition.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/PropertyCondition.java @@ -24,22 +24,22 @@ */ package de.bluecolored.bluemap.core.resourcepack; -import com.google.common.base.Preconditions; +import de.bluecolored.bluemap.core.util.Preconditions; import de.bluecolored.bluemap.core.world.BlockState; @FunctionalInterface public interface PropertyCondition { - static final PropertyCondition MATCH_ALL = new All(); - static final PropertyCondition MATCH_NONE = new None(); + PropertyCondition MATCH_ALL = new All(); + PropertyCondition MATCH_NONE = new None(); boolean matches(BlockState state); - public class Property implements PropertyCondition { + class Property implements PropertyCondition { - private String key; - private String value; + private final String key; + private final String value; private Property (String key, String value) { this.key = key.toLowerCase(); @@ -57,7 +57,7 @@ public interface PropertyCondition { class And implements PropertyCondition { - private PropertyCondition[] conditions; + private final PropertyCondition[] conditions; private And (PropertyCondition... conditions) { Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!"); @@ -77,7 +77,7 @@ public interface PropertyCondition { class Or implements PropertyCondition { - private PropertyCondition[] conditions; + private final PropertyCondition[] conditions; private Or (PropertyCondition... conditions) { Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java index 672acde5..3f94e712 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java @@ -24,24 +24,8 @@ */ package de.bluecolored.bluemap.core.resourcepack; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import javax.imageio.ImageIO; - -import org.apache.commons.io.output.ByteArrayOutputStream; - -import com.google.common.collect.Multimap; -import com.google.common.collect.MultimapBuilder; - import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.resourcepack.BlockStateResource.Builder; import de.bluecolored.bluemap.core.resourcepack.fileaccess.BluemapAssetOverrideFileAccess; @@ -49,10 +33,17 @@ import de.bluecolored.bluemap.core.resourcepack.fileaccess.CaseInsensitiveFileAc import de.bluecolored.bluemap.core.resourcepack.fileaccess.CombinedFileAccess; import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; import de.bluecolored.bluemap.core.world.BlockState; +import org.apache.commons.io.output.ByteArrayOutputStream; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.*; /** * Represents all resources (BlockStates / BlockModels and Textures) that are loaded and used to generate map-models. */ +@DebugDump public class ResourcePack { private static final String[] CONFIG_FILES = { @@ -62,18 +53,18 @@ public class ResourcePack { "biomes.json" }; - private MinecraftVersion minecraftVersion; + private final MinecraftVersion minecraftVersion; protected Map blockStateResources; protected Map blockModelResources; protected TextureGallery textures; - private BlockColorCalculator blockColorCalculator; + private final BlockColorCalculator blockColorCalculator; private BufferedImage foliageMap; private BufferedImage grassMap; - private Multimap configs; + private Map> configs; public ResourcePack(MinecraftVersion minecraftVersion) { this.minecraftVersion = minecraftVersion; @@ -86,14 +77,14 @@ public class ResourcePack { grassMap = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); grassMap.setRGB(0, 0, 0xFF00FF00); blockColorCalculator = new BlockColorCalculator(foliageMap, grassMap); - configs = MultimapBuilder.hashKeys().arrayListValues().build(); + configs = new HashMap<>(); } /** * Returns all config-files found in the namespaces of the ResourcePack with that filename */ public Collection getConfigAdditions(String configFileName){ - return configs.get(configFileName); + return configs.getOrDefault(configFileName, Collections.emptyList()); } /** @@ -121,7 +112,7 @@ public class ResourcePack { * @param sources The list of {@link File} sources. Each can be a folder or any zip-compressed file. (E.g. .zip or .jar) */ public void load(Collection sources) throws IOException, InterruptedException { - load(sources.toArray(new File[sources.size()])); + load(sources.toArray(new File[0])); } /** @@ -134,10 +125,9 @@ public class ResourcePack { */ public void load(File... sources) throws InterruptedException { try (CombinedFileAccess combinedSources = new CombinedFileAccess()){ - for (int i = 0; i < sources.length; i++) { + for (File file : sources) { if (Thread.interrupted()) throw new InterruptedException(); - - File file = sources[i]; + try { combinedSources.addFileAccess(FileAccess.of(file)); } catch (IOException e) { @@ -179,8 +169,9 @@ public class ResourcePack { //load configs for (String configName : CONFIG_FILES) { try { - Resource config = new Resource(sourcesAccess.readFile("assets/" + namespace + "/" + configName)); - configs.put(configName, config); + Resource config = new Resource(sourcesAccess.readFile( + "assets/" + namespace + "/" + configName)); + configs.computeIfAbsent(configName, t -> new ArrayList<>()).add(config); } catch (FileNotFoundException ignore) { } catch (IOException ex) { Logger.global.logError("Failed to load config for " + namespace + ": " + configName, ex); @@ -251,9 +242,9 @@ public class ResourcePack { /** * Caches a full InputStream in a byte-array that can be read later */ - public class Resource { + public static class Resource { - private byte[] data; + private final byte[] data; public Resource(InputStream data) throws FileNotFoundException, IOException { try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java index 689105b5..42f2980a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java @@ -222,8 +222,9 @@ public class TextureGallery { try { loadTexture(fileAccess, texture.getPath()); } catch (IOException e) { - Logger.global.logWarning("Failed to reload texture: " + texture.getPath()); - Logger.global.noFloodWarning("This happens if the resource-packs have changed, but you have not deleted your generated maps. This might result in broken map-models!"); + Logger.global.noFloodWarning("TextureGallery-uiz78tef5", "Failed to reload texture: " + texture.getPath()); + Logger.global.noFloodWarning("TextureGallery-89763455h", "This happens if the resource-packs have changed, but you have not deleted your already generated maps. This might result in broken map-models!"); + Logger.global.noFloodWarning("TextureGallery-re56ugb56", "(Future warnings of this will be suppressed, so more textures might have failed to load after this)"); } } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/threejs/BufferAttribute.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/threejs/BufferAttribute.java index 48710df5..ae78edb2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/threejs/BufferAttribute.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/threejs/BufferAttribute.java @@ -27,14 +27,14 @@ */ package de.bluecolored.bluemap.core.threejs; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import de.bluecolored.bluemap.core.util.Preconditions; + import java.io.IOException; import java.util.ArrayList; import java.util.List; -import com.google.common.base.Preconditions; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; - /** * Represents a ThreeJS BufferAttribute */ @@ -118,7 +118,7 @@ public class BufferAttribute { if(name.equals("array")){ json.beginArray(); //array while (json.hasNext()){ - list.add(new Float(json.nextDouble())); + list.add((float) json.nextDouble()); } json.endArray(); //array } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AABB.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AABB.java deleted file mode 100644 index 48e66349..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AABB.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * This file is part of SpongeAPI, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * 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.util; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.flowpowered.math.GenericMath; -import com.flowpowered.math.vector.Vector3d; -import com.flowpowered.math.vector.Vector3i; - -/** - * An axis aligned bounding box. That is, an un-rotated cuboid. - * It is represented by its minimum and maximum corners. - * - * Using integers, the box has a minimum size of 1 in each direction. - * - *

This class is immutable, all objects returned are either new instances or - * itself.

- */ -public class AABB { - - private final Vector3i min; - private final Vector3i max; - private Vector3i size = null; - private Vector3d center = null; - - /** - * Constructs a new bounding box from two opposite corners. - * Fails the resulting box would be degenerate (a dimension is 0). - * - * @param x1 The first corner x coordinate - * @param y1 The first corner y coordinate - * @param z1 The first corner z coordinate - * @param x2 The second corner x coordinate - * @param y2 The second corner y coordinate - * @param z2 The second corner z coordinate - */ - public AABB(int x1, int y1, int z1, int x2, int y2, int z2) { - this(new Vector3i(x1, y1, z1), new Vector3i(x2, y2, z2)); - } - - /** - * Constructs a new bounding box from two opposite corners. - * Fails the resulting box would be degenerate (a dimension is 0). - * - * @param firstCorner The first corner - * @param secondCorner The second corner - */ - public AABB(Vector3i firstCorner, Vector3i secondCorner) { - checkNotNull(firstCorner, "firstCorner"); - checkNotNull(secondCorner, "secondCorner"); - this.min = firstCorner.min(secondCorner); - this.max = firstCorner.max(secondCorner); - } - - /** - * The minimum corner of the box. - * - * @return The minimum corner - */ - public Vector3i getMin() { - return this.min; - } - - /** - * The maximum corner of the box. - * - * @return The maximum corner - */ - public Vector3i getMax() { - return this.max; - } - - /** - * Returns the center of the box, halfway between each corner. - * - * @return The center - */ - public Vector3d getCenter() { - if (this.center == null) { - this.center = this.min.toDouble().add(getSize().toDouble().div(2)); - } - return this.center; - } - - /** - * Gets the size of the box. - * - * @return The size - */ - public Vector3i getSize() { - if (this.size == null) { - this.size = this.max.sub(this.min).add(Vector3i.ONE); - } - return this.size; - } - - /** - * Checks if the bounding box contains a point. - * - * @param point The point to check - * @return Whether or not the box contains the point - */ - public boolean contains(Vector3i point) { - checkNotNull(point, "point"); - return contains(point.getX(), point.getY(), point.getZ()); - } - - /** - * Checks if the bounding box contains a point. - * - * @param point The point to check - * @return Whether or not the box contains the point - */ - public boolean contains(Vector3d point) { - checkNotNull(point, "point"); - return contains(point.getX(), point.getY(), point.getZ()); - } - - /** - * Checks if the bounding box contains a point. - * - * @param x The x coordinate of the point - * @param y The y coordinate of the point - * @param z The z coordinate of the point - * @return Whether or not the box contains the point - */ - public boolean contains(double x, double y, double z) { - return contains(GenericMath.floor(x), GenericMath.floor(y), GenericMath.floor(z)); - } - - /** - * Checks if the bounding box contains a point. - * - * @param x The x coordinate of the point - * @param y The y coordinate of the point - * @param z The z coordinate of the point - * @return Whether or not the box contains the point - */ - public boolean contains(int x, int y, int z) { - return this.min.getX() <= x && this.max.getX() >= x - && this.min.getY() <= y && this.max.getY() >= y - && this.min.getZ() <= z && this.max.getZ() >= z; - } - - /** - * Checks if the bounding box intersects another. - * - * @param other The other bounding box to check - * @return Whether this bounding box intersects with the other - */ - public boolean intersects(AABB other) { - checkNotNull(other, "other"); - return this.max.getX() >= other.getMin().getX() && other.getMax().getX() >= this.min.getX() - && this.max.getY() >= other.getMin().getY() && other.getMax().getY() >= this.min.getY() - && this.max.getZ() >= other.getMin().getZ() && other.getMax().getZ() >= this.min.getZ(); - } - - /** - * Offsets this bounding box by a given amount and returns a new box. - * - * @param offset The offset to apply - * @return The new offset box - */ - public AABB offset(Vector3i offset) { - checkNotNull(offset, "offset"); - return offset(offset.getX(), offset.getY(), offset.getZ()); - } - - /** - * Offsets this bounding box by a given amount and returns a new box. - * - * @param x The amount of offset for the x coordinate - * @param y The amount of offset for the y coordinate - * @param z The amount of offset for the z coordinate - * @return The new offset box - */ - public AABB offset(int x, int y, int z) { - return new AABB(this.min.add(x, y, z), this.max.add(x, y, z)); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (!(other instanceof AABB)) { - return false; - } - final AABB aabb = (AABB) other; - return this.min.equals(aabb.min) && this.max.equals(aabb.max); - - } - - @Override - public int hashCode() { - int result = this.min.hashCode(); - result = 31 * result + this.max.hashCode(); - return result; - } - - @Override - public String toString() { - return "AABB(" + this.min + " to " + this.max + ")"; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AtomicFileHelper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AtomicFileHelper.java new file mode 100644 index 00000000..cd584b55 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AtomicFileHelper.java @@ -0,0 +1,95 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.util; + +import java.io.*; +import java.nio.file.*; + +public class AtomicFileHelper { + + public static OutputStream createFilepartOutputStream(final File file) throws IOException { + return createFilepartOutputStream(file.toPath()); + } + + public static OutputStream createFilepartOutputStream(final Path file) throws IOException { + final Path partFile = getPartFile(file); + Files.createDirectories(partFile.getParent()); + + OutputStream os = Files.newOutputStream(partFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + return new WrappedOutputStream(os, () -> { + Files.deleteIfExists(file); + Files.createDirectories(file.getParent()); + + try { + Files.move(partFile, file, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException ex) { + Files.move(partFile, file); + } + }); + } + + private static Path getPartFile(Path file) { + return file.normalize().getParent().resolve(file.getFileName() + ".filepart"); + } + + private static class WrappedOutputStream extends OutputStream { + + private final OutputStream out; + private final ThrowingRunnable onClose; + + private WrappedOutputStream(OutputStream out, ThrowingRunnable onClose) { + this.out = out; + 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 + public void close() throws IOException { + out.close(); + onClose.run(); + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java index 3da8e288..665e6caa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java @@ -25,7 +25,8 @@ package de.bluecolored.bluemap.core.util; import com.flowpowered.math.vector.Vector3i; -import com.google.common.base.Preconditions; + +import java.util.Objects; public enum Axis { @@ -44,7 +45,7 @@ public enum Axis { } public static Axis fromString(String name){ - Preconditions.checkNotNull(name); + Objects.requireNonNull(name); return valueOf(name.toUpperCase()); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtils.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtils.java index ebfb380c..334708db 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtils.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtils.java @@ -24,23 +24,20 @@ */ package de.bluecolored.bluemap.core.util; +import com.flowpowered.math.vector.*; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.serialize.SerializationException; + import java.util.List; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3f; -import com.flowpowered.math.vector.Vector3i; -import com.flowpowered.math.vector.Vector4f; -import com.flowpowered.math.vector.Vector4i; - -import ninja.leaping.configurate.ConfigurationNode; - public class ConfigUtils { private ConfigUtils(){} public static Vector2i readVector2i(ConfigurationNode vectorNode){ if (vectorNode.isList()){ - List list = vectorNode.getChildrenList(); + List list = vectorNode.childrenList(); return new Vector2i( list.get(0).getInt(), list.get(1).getInt() @@ -48,14 +45,14 @@ public class ConfigUtils { } return new Vector2i( - vectorNode.getNode("x").getInt(), - vectorNode.getNode("y").getInt() + vectorNode.node("x").getInt(), + vectorNode.node("y").getInt() ); } public static Vector3i readVector3i(ConfigurationNode vectorNode){ if (vectorNode.isList()){ - List list = vectorNode.getChildrenList(); + List list = vectorNode.childrenList(); return new Vector3i( list.get(0).getInt(), list.get(1).getInt(), @@ -64,15 +61,15 @@ public class ConfigUtils { } return new Vector3i( - vectorNode.getNode("x").getInt(), - vectorNode.getNode("y").getInt(), - vectorNode.getNode("z").getInt() + vectorNode.node("x").getInt(), + vectorNode.node("y").getInt(), + vectorNode.node("z").getInt() ); } public static Vector3f readVector3f(ConfigurationNode vectorNode){ if (vectorNode.isList()){ - List list = vectorNode.getChildrenList(); + List list = vectorNode.childrenList(); return new Vector3f( list.get(0).getFloat(), list.get(1).getFloat(), @@ -81,15 +78,15 @@ public class ConfigUtils { } return new Vector3f( - vectorNode.getNode("x").getFloat(), - vectorNode.getNode("y").getFloat(), - vectorNode.getNode("z").getFloat() + vectorNode.node("x").getFloat(), + vectorNode.node("y").getFloat(), + vectorNode.node("z").getFloat() ); } public static Vector4i readVector4i(ConfigurationNode vectorNode){ if (vectorNode.isList()){ - List list = vectorNode.getChildrenList(); + List list = vectorNode.childrenList(); return new Vector4i( list.get(0).getInt(), list.get(1).getInt(), @@ -99,16 +96,16 @@ public class ConfigUtils { } return new Vector4i( - vectorNode.getNode("x").getInt(), - vectorNode.getNode("y").getInt(), - vectorNode.getNode("z").getInt(), - vectorNode.getNode("w").getInt() + vectorNode.node("x").getInt(), + vectorNode.node("y").getInt(), + vectorNode.node("z").getInt(), + vectorNode.node("w").getInt() ); } public static Vector4f readVector4f(ConfigurationNode vectorNode){ if (vectorNode.isList()){ - List list = vectorNode.getChildrenList(); + List list = vectorNode.childrenList(); return new Vector4f( list.get(0).getFloat(), list.get(1).getFloat(), @@ -118,18 +115,18 @@ public class ConfigUtils { } return new Vector4f( - vectorNode.getNode("x").getFloat(), - vectorNode.getNode("y").getFloat(), - vectorNode.getNode("z").getFloat(), - vectorNode.getNode("w").getFloat() + vectorNode.node("x").getFloat(), + vectorNode.node("y").getFloat(), + vectorNode.node("z").getFloat(), + vectorNode.node("w").getFloat() ); } - public static void writeVector4f(ConfigurationNode vectorNode, Vector4f v){ - vectorNode.appendListNode().setValue(v.getX()); - vectorNode.appendListNode().setValue(v.getY()); - vectorNode.appendListNode().setValue(v.getZ()); - vectorNode.appendListNode().setValue(v.getW()); + public static void writeVector4f(ConfigurationNode vectorNode, Vector4f v) throws SerializationException { + vectorNode.appendListNode().set(v.getX()); + vectorNode.appendListNode().set(v.getY()); + vectorNode.appendListNode().set(v.getZ()); + vectorNode.appendListNode().set(v.getW()); } /** @@ -139,7 +136,7 @@ public class ConfigUtils { * @throws NumberFormatException If the value is not formatted correctly or if there is no value present. */ public static int readColorInt(ConfigurationNode node) throws NumberFormatException { - Object value = node.getValue(); + Object value = node.raw(); if (value == null) throw new NumberFormatException("No value!"); @@ -161,10 +158,10 @@ public class ConfigUtils { } public static String nodePathToString(ConfigurationNode node) { - Object[] keys = node.getPath(); - String[] stringKeys = new String[keys.length]; - for (int i = 0; i < keys.length; i++) { - stringKeys[i] = keys[i].toString(); + NodePath keys = node.path(); + String[] stringKeys = new String[keys.size()]; + for (int i = 0; i < keys.size(); i++) { + stringKeys[i] = keys.get(i).toString(); } return String.join(".", stringKeys); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java index 3016897a..6b4000d2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java @@ -25,7 +25,9 @@ package de.bluecolored.bluemap.core.util; import com.flowpowered.math.vector.Vector3i; -import com.google.common.base.Preconditions; + +import java.util.Objects; + public enum Direction { @@ -90,7 +92,7 @@ public enum Direction { } public static Direction fromString(String name){ - Preconditions.checkNotNull(name); + Objects.requireNonNull(name); return valueOf(name.toUpperCase()); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtils.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtils.java index fd5e5370..f64c59d0 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtils.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtils.java @@ -82,21 +82,6 @@ public class FileUtils { return p.resolve(fileName + "." + fileType).toFile(); } - /** - * Blocks until a file can be read and written.
- * (Do not use this method to sync file-access from different threads!) - */ - public static void waitForFile(File file, long time, TimeUnit unit) throws InterruptedException, TimeoutException { - long start = System.currentTimeMillis(); - long timeout = start + TimeUnit.MILLISECONDS.convert(time, unit); - long sleepTime = 1; - while(!file.canWrite() || !file.canRead()){ - Thread.sleep(sleepTime); - sleepTime = (long) Math.min(Math.ceil(sleepTime * 1.5), 1000); - if (System.currentTimeMillis() > timeout) throw new TimeoutException(); - } - } - /** * The path-elements are being matched to the pattern-elements, * each pattern-element can be a regex pattern to match against one path-element or "*" to represent any number of arbitrary elements (lazy: until the next pattern matches). diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Lazy.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Lazy.java index e13427c0..aeeead7d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Lazy.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Lazy.java @@ -24,19 +24,28 @@ */ package de.bluecolored.bluemap.core.util; +import de.bluecolored.bluemap.core.debug.DebugDump; + +import java.util.Objects; import java.util.function.Supplier; public class Lazy { private Supplier loader; + + @DebugDump private T value; public Lazy(Supplier loader) { + Objects.requireNonNull(loader); + this.loader = loader; this.value = null; } public Lazy(T value) { + Objects.requireNonNull(value); + this.loader = null; this.value = value; } @@ -44,6 +53,7 @@ public class Lazy { public T getValue() { if (!isLoaded()) { this.value = loader.get(); + this.loader = null; } return this.value; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java index db9ff38b..979b240d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java @@ -28,6 +28,7 @@ import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector4f; +import de.bluecolored.bluemap.core.model.VectorM3f; public class MathUtils { @@ -72,6 +73,27 @@ public class MathUtils { return n; } + /** + * Calculates the surface-normal of a plane spanned between three vectors. + * @param p1 The first vector + * @param p2 The second vector + * @param p3 The third vector + * @return The calculated normal + */ + public static VectorM3f getSurfaceNormal(VectorM3f p1, VectorM3f p2, VectorM3f p3) { + float ux = p2.x - p1.x, uy = p2.y - p1.y, uz = p2.z - p1.z; + float vx = p3.x - p1.x, vy = p3.y - p1.y, vz = p3.z - p1.z; + + float nX = uy * vz - uz * vy; + float nY = uz * vx - ux * vz; + float nZ = ux * vy - uy * vx; + + VectorM3f n = new VectorM3f(nX, nY, nZ); + n.normalize(); + + return n; + } + /** * Hashes the provided position to a random float between 0 and 1.
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Preconditions.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Preconditions.java new file mode 100644 index 00000000..5474da03 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Preconditions.java @@ -0,0 +1,37 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.util; + +public class Preconditions { + + public static void checkArgument(boolean argument) throws IllegalArgumentException { + if (!argument) throw new IllegalArgumentException(); + } + + public static void checkArgument(boolean argument, String message) throws IllegalArgumentException { + if (!argument) throw new IllegalArgumentException(message); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ThrowingRunnable.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ThrowingRunnable.java new file mode 100644 index 00000000..8290b7d9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ThrowingRunnable.java @@ -0,0 +1,32 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.util; + +@FunctionalInterface +public interface ThrowingRunnable { + + void run() throws E; + +} 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 deleted file mode 100644 index 3d388491..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.web; - -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3f; -import de.bluecolored.bluemap.core.config.MapConfig; -import de.bluecolored.bluemap.core.render.TileRenderer; -import de.bluecolored.bluemap.core.util.FileUtils; -import de.bluecolored.bluemap.core.util.MathUtils; -import de.bluecolored.bluemap.core.world.World; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.gson.GsonConfigurationLoader; -import ninja.leaping.configurate.loader.ConfigurationLoader; - -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.stream.Collectors; - -public class WebSettings { - - private ConfigurationLoader configLoader; - private ConfigurationNode rootNode; - - public WebSettings(File settingsFile) throws IOException { - FileUtils.createFile(settingsFile); - - configLoader = GsonConfigurationLoader.builder() - .setFile(settingsFile) - .build(); - - load(); - } - - public void load() throws IOException { - rootNode = configLoader.load(); - } - - public void save() throws IOException { - configLoader.save(rootNode); - } - - public void set(Object value, Object... path) { - rootNode.getNode(path).setValue(value); - } - - public Object get(Object... path) { - return rootNode.getNode(path).getValue(); - } - - public String getString(Object... path) { - return rootNode.getNode(path).getString(); - } - - public int getInt(Object... path) { - return rootNode.getNode(path).getInt(); - } - - public long getLong(Object... path) { - return rootNode.getNode(path).getLong(); - } - - public float getFloat(Object... path) { - return rootNode.getNode(path).getFloat(); - } - - public double getDouble(Object... path) { - return rootNode.getNode(path).getDouble(); - } - - public Collection getMapIds() { - return rootNode.getNode("maps").getChildrenMap().keySet().stream().map(o -> o.toString()).collect(Collectors.toSet()); - } - - public void setAllMapsEnabled(boolean enabled) { - for (ConfigurationNode mapNode : rootNode.getNode("maps").getChildrenMap().values()) { - mapNode.getNode("enabled").setValue(enabled); - } - } - - public void setMapEnabled(boolean enabled, String mapId) { - set(enabled, "maps", mapId, "enabled"); - } - - public void setFrom(TileRenderer tileRenderer, String mapId) { - Vector2i hiresTileSize = tileRenderer.getHiresModelManager().getTileSize(); - Vector2i gridOrigin = tileRenderer.getHiresModelManager().getGridOrigin(); - Vector2i lowresTileSize = tileRenderer.getLowresModelManager().getTileSize(); - Vector2i lowresPointsPerHiresTile = tileRenderer.getLowresModelManager().getPointsPerHiresTile(); - - set(hiresTileSize.getX(), "maps", mapId, "hires", "tileSize", "x"); - set(hiresTileSize.getY(), "maps", mapId, "hires", "tileSize", "z"); - set(1, "maps", mapId, "hires", "scale", "x"); - set(1, "maps", mapId, "hires", "scale", "z"); - set(gridOrigin.getX(), "maps", mapId, "hires", "translate", "x"); - set(gridOrigin.getY(), "maps", mapId, "hires", "translate", "z"); - - Vector2i pointSize = hiresTileSize.div(lowresPointsPerHiresTile); - Vector2i tileSize = pointSize.mul(lowresTileSize); - - set(tileSize.getX(), "maps", mapId, "lowres", "tileSize", "x"); - set(tileSize.getY(), "maps", mapId, "lowres", "tileSize", "z"); - set(pointSize.getX(), "maps", mapId, "lowres", "scale", "x"); - set(pointSize.getY(), "maps", mapId, "lowres", "scale", "z"); - set(pointSize.getX() / 2, "maps", mapId, "lowres", "translate", "x"); - set(pointSize.getY() / 2, "maps", mapId, "lowres", "translate", "z"); - } - - public void setFrom(World world, String mapId) { - set(world.getSpawnPoint().getX(), "maps", mapId, "startPos", "x"); - set(world.getSpawnPoint().getZ(), "maps", mapId, "startPos", "z"); - set(world.getUUID().toString(), "maps", mapId, "world"); - } - - public void setFrom(MapConfig mapConfig, String mapId) { - Vector2i startPos = mapConfig.getStartPos(); - if (startPos != null) { - set(startPos.getX(), "maps", mapId, "startPos", "x"); - set(startPos.getY(), "maps", mapId, "startPos", "z"); - } - - Vector3f skyColor = MathUtils.color3FromInt(mapConfig.getSkyColor()); - set(skyColor.getX(), "maps", mapId, "skyColor", "r"); - set(skyColor.getY(), "maps", mapId, "skyColor", "g"); - set(skyColor.getZ(), "maps", mapId, "skyColor", "b"); - - set(mapConfig.getAmbientLight(), "maps", mapId, "ambientLight"); - - setName(mapConfig.getName(), mapId); - } - - public void setOrdinal(int ordinal, String mapId) { - set(ordinal, "maps", mapId, "ordinal"); - } - - public int getOrdinal(String mapId) { - return getInt("maps", mapId, "ordinal"); - } - - public void setName(String name, String mapId) { - set(name, "maps", mapId, "name"); - } - - public String getName(String mapId) { - return getString("maps", mapId, "name"); - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java index eeca2ad4..1b147ac7 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java @@ -24,36 +24,39 @@ */ package de.bluecolored.bluemap.core.webserver; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import de.bluecolored.bluemap.core.logger.Logger; + +import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; -import java.util.concurrent.TimeUnit; - -import de.bluecolored.bluemap.core.logger.Logger; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; public class HttpConnection implements Runnable { - private HttpRequestHandler handler; + private final HttpRequestHandler handler; - private ServerSocket server; - private Socket connection; - private InputStream in; - private OutputStream out; + private final ServerSocket server; + private final Socket connection; + private final InputStream in; + private final OutputStream out; - private final boolean verbose; + private final Semaphore processingSemaphore; - public HttpConnection(ServerSocket server, Socket connection, HttpRequestHandler handler, int timeout, TimeUnit timeoutUnit, boolean verbose) throws IOException { + private final boolean verbose; + + public HttpConnection(ServerSocket server, Socket connection, HttpRequestHandler handler, Semaphore processingSemaphore, int timeout, TimeUnit timeoutUnit, boolean verbose) throws IOException { this.server = server; this.connection = connection; this.handler = handler; - this.verbose = verbose; + this.verbose = verbose; + + this.processingSemaphore = processingSemaphore; if (isClosed()){ throw new IOException("Socket already closed!"); @@ -61,8 +64,8 @@ public class HttpConnection implements Runnable { connection.setSoTimeout((int) timeoutUnit.toMillis(timeout)); - in = this.connection.getInputStream(); - out = this.connection.getOutputStream(); + in = new BufferedInputStream(this.connection.getInputStream()); + out = new BufferedOutputStream(this.connection.getOutputStream()); } @Override @@ -70,25 +73,30 @@ public class HttpConnection implements Runnable { while (!isClosed() && !server.isClosed()){ try { HttpRequest request = acceptRequest(); - HttpResponse response = handler.handle(request); - sendResponse(response); - if (verbose) { - log(request, response); + + try { + processingSemaphore.acquire(); + + HttpResponse response = handler.handle(request); + sendResponse(response); + + if (verbose) log(request, response); + } finally { + processingSemaphore.release(); } + } catch (InvalidRequestException e){ try { sendResponse(new HttpResponse(HttpStatusCode.BAD_REQUEST)); - } catch (IOException e1) {} + } catch (IOException ignored) {} break; - } catch (SocketTimeoutException e) { - break; - } catch (SocketException e){ - break; - } catch (ConnectionClosedException e){ + } catch (SocketTimeoutException | ConnectionClosedException | SocketException e) { break; } catch (IOException e) { Logger.global.logError("Unexpected error while processing a HttpRequest!", e); break; + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); } } @@ -128,15 +136,7 @@ public class HttpConnection implements Runnable { } public void close() throws IOException { - try { - in.close(); - } finally { - try { - out.close(); - } finally { - connection.close(); - } - } + connection.close(); } public static class ConnectionClosedException extends IOException { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequest.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequest.java index 30df3462..7b3386aa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequest.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequest.java @@ -24,33 +24,22 @@ */ package de.bluecolored.bluemap.core.webserver; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import de.bluecolored.bluemap.core.webserver.HttpConnection.ConnectionClosedException; import de.bluecolored.bluemap.core.webserver.HttpConnection.InvalidRequestException; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class HttpRequest { private static final Pattern REQUEST_PATTERN = Pattern.compile("^(\\w+) (\\S+) (.+)$"); private String method; - private String adress; + private String address; private String version; private Map> header; private Map> headerLC; @@ -60,9 +49,9 @@ public class HttpRequest { private Map getParams = null; private String getParamString = null; - public HttpRequest(String method, String adress, String version, Map> header) { + public HttpRequest(String method, String address, String version, Map> header) { this.method = method; - this.adress = adress; + this.address = address; this.version = version; this.header = header; this.headerLC = new HashMap<>(); @@ -83,8 +72,8 @@ public class HttpRequest { return method; } - public String getAdress(){ - return adress; + public String getAddress(){ + return address; } public String getVersion() { @@ -127,7 +116,7 @@ public class HttpRequest { } private void parseAdress() { - String adress = this.adress; + String adress = this.address; if (adress.isEmpty()) adress = "/"; String[] adressParts = adress.split("\\?", 2); String path = adressParts[0]; @@ -167,8 +156,8 @@ public class HttpRequest { String method = m.group(1); if (method == null) throw new InvalidRequestException(); - String adress = m.group(2); - if (adress == null) throw new InvalidRequestException(); + String address = m.group(2); + if (address == null) throw new InvalidRequestException(); String version = m.group(3); if (version == null) throw new InvalidRequestException(); @@ -192,7 +181,7 @@ public class HttpRequest { headerMap.put(kv[0].trim(), values); } - HttpRequest request = new HttpRequest(method, adress, version, headerMap); + HttpRequest request = new HttpRequest(method, address, version, headerMap); if (request.getLowercaseHeader("Transfer-Encoding").contains("chunked")){ try { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java index 2442ce71..969e3792 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java @@ -24,22 +24,13 @@ */ package de.bluecolored.bluemap.core.webserver; -import java.io.ByteArrayInputStream; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - import org.apache.commons.lang3.StringUtils; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.Map.Entry; + public class HttpResponse implements Closeable { private String version; @@ -57,22 +48,12 @@ public class HttpResponse implements Closeable { } public void addHeader(String key, String value){ - Set valueSet = header.get(key); - if (valueSet == null){ - valueSet = new HashSet<>(); - header.put(key, valueSet); - } - + Set valueSet = header.computeIfAbsent(key, k -> new HashSet<>()); valueSet.add(value); } public void removeHeader(String key, String value){ - Set valueSet = header.get(key); - if (valueSet == null){ - valueSet = new HashSet<>(); - header.put(key, valueSet); - } - + Set valueSet = header.computeIfAbsent(key, k -> new HashSet<>()); valueSet.remove(value); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/WebServer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/WebServer.java index 6940eae5..de722835 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/WebServer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/WebServer.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.core.webserver; +import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; import java.io.IOException; @@ -33,6 +34,7 @@ import java.net.Socket; import java.net.SocketException; import java.util.concurrent.*; +@DebugDump public class WebServer extends Thread { private final int port; @@ -41,6 +43,7 @@ public class WebServer extends Thread { private final boolean verbose; private final HttpRequestHandler handler; + private final Semaphore processingSemaphore; private ThreadPoolExecutor connectionThreads; @@ -57,6 +60,7 @@ public class WebServer extends Thread { this.verbose = verbose; this.handler = handler; + this.processingSemaphore = new Semaphore(18); connectionThreads = null; } @@ -90,7 +94,7 @@ public class WebServer extends Thread { Socket connection = server.accept(); try { - connectionThreads.execute(new HttpConnection(server, connection, handler, 10, TimeUnit.SECONDS, verbose)); + connectionThreads.execute(new HttpConnection(server, connection, handler, processingSemaphore, 10, TimeUnit.SECONDS, verbose)); } catch (RejectedExecutionException e){ connection.close(); Logger.global.logWarning("Dropped an incoming HttpConnection! (Too many connections?)"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java index 9ed40b63..a76f2032 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java @@ -26,11 +26,10 @@ package de.bluecolored.bluemap.core.world; import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector4f; -import com.google.common.base.MoreObjects; import de.bluecolored.bluemap.core.util.ConfigUtils; import de.bluecolored.bluemap.core.util.MathUtils; -import ninja.leaping.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; public class Biome { @@ -94,27 +93,27 @@ public class Biome { Biome biome = new Biome(); biome.id = id; - biome.numeralId = node.getNode("id").getInt(biome.numeralId); - biome.humidity = node.getNode("humidity").getFloat(biome.humidity); - biome.temp = node.getNode("temp").getFloat(biome.temp); - try { biome.waterColor = MathUtils.color3FromInt(ConfigUtils.readColorInt(node.getNode("watercolor"))); } catch (NumberFormatException ignored) {} - try { biome.overlayFoliageColor = MathUtils.color4FromInt(ConfigUtils.readColorInt(node.getNode("foliagecolor"))); } catch (NumberFormatException ignored) {} - try { biome.overlayGrassColor = MathUtils.color4FromInt(ConfigUtils.readColorInt(node.getNode("grasscolor"))); } catch (NumberFormatException ignored) {} + biome.numeralId = node.node("id").getInt(biome.numeralId); + biome.humidity = node.node("humidity").getFloat(biome.humidity); + biome.temp = node.node("temp").getFloat(biome.temp); + try { biome.waterColor = MathUtils.color3FromInt(ConfigUtils.readColorInt(node.node("watercolor"))); } catch (NumberFormatException ignored) {} + try { biome.overlayFoliageColor = MathUtils.color4FromInt(ConfigUtils.readColorInt(node.node("foliagecolor"))); } catch (NumberFormatException ignored) {} + try { biome.overlayGrassColor = MathUtils.color4FromInt(ConfigUtils.readColorInt(node.node("grasscolor"))); } catch (NumberFormatException ignored) {} return biome; } @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("id", getId()) - .add("numeralId", getNumeralId()) - .add("humidity", getHumidity()) - .add("temp", getTemp()) - .add("waterColor", getWaterColor()) - .add("overlayFoliageColor", getOverlayFoliageColor()) - .add("overlayGrassColor", getOverlayGrassColor()) - .toString(); + return "Biome{" + + "id='" + id + '\'' + + ", numeralId=" + numeralId + + ", humidity=" + humidity + + ", temp=" + temp + + ", waterColor=" + waterColor + + ", overlayFoliageColor=" + overlayFoliageColor + + ", overlayGrassColor=" + overlayGrassColor + + '}'; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java index c6059f37..be85aedd 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java @@ -25,8 +25,6 @@ package de.bluecolored.bluemap.core.world; import com.flowpowered.math.vector.Vector3i; -import com.google.common.base.MoreObjects; - import de.bluecolored.bluemap.core.util.Direction; public class Block { @@ -163,13 +161,13 @@ public class Block { @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("pos", getPosition()) - .add("biome", getBiome()) - .add("blocklight", getBlockLightLevel()) - .add("sunlight", getSunLightLevel()) - .add("state", getBlockState()) - .toString(); + return "Block{" + + "blockState=" + blockState + + ", biome=" + biome + + ", pos=" + pos + + ", sunLight=" + sunLight + + ", blockLight=" + blockLight + + '}'; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java index ac829d51..9a525584 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java @@ -42,7 +42,7 @@ import java.util.StringJoiner; */ public class BlockState { - private static Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.*)\\])?$"); + private static final Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.*)\\])?$"); public static final BlockState AIR = new BlockState("minecraft:air", Collections.emptyMap()); public static final BlockState MISSING = new BlockState("bluemap:missing", Collections.emptyMap()); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java new file mode 100644 index 00000000..08f121f7 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java @@ -0,0 +1,31 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 interface Chunk { + + boolean isGenerated(); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Grid.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Grid.java new file mode 100644 index 00000000..b5a90021 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Grid.java @@ -0,0 +1,151 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + +import com.flowpowered.math.vector.Vector2i; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +public class Grid { + + public static final Grid UNIT = new Grid(Vector2i.ONE); + + private final Vector2i gridSize; + private final Vector2i offset; + + public Grid(int gridSize) { + this(gridSize, 0); + } + + public Grid(int gridSize, int offset) { + this(new Vector2i(gridSize, gridSize), new Vector2i(offset, offset)); + } + + public Grid(Vector2i gridSize) { + this(gridSize, Vector2i.ZERO); + } + + public Grid(Vector2i gridSize, Vector2i offset) { + Objects.requireNonNull(gridSize); + Objects.requireNonNull(offset); + + gridSize = gridSize.max(1,1); + + this.gridSize = gridSize; + this.offset = offset; + } + + public Vector2i getGridSize() { + return gridSize; + } + + public Vector2i getOffset() { + return offset; + } + + public Vector2i getCell(Vector2i pos) { + return new Vector2i( + Math.floorDiv(pos.getX() - offset.getX(), gridSize.getX()), + Math.floorDiv(pos.getY() - offset.getY(), gridSize.getY()) + ); + } + + public Vector2i getCellMin(Vector2i cell) { + return new Vector2i( + cell.getX() * gridSize.getX() + offset.getX(), + cell.getY() * gridSize.getY() + offset.getY() + ); + } + + public Vector2i getCellMax(Vector2i cell) { + return new Vector2i( + (cell.getX() + 1) * gridSize.getX() + offset.getX() - 1, + (cell.getY() + 1) * gridSize.getY() + offset.getY() - 1 + ); + } + + public Vector2i getCellMin(Vector2i cell, Grid targetGrid) { + return targetGrid.getCell(getCellMin(cell)); + } + + public Vector2i getCellMax(Vector2i cell, Grid targetGrid) { + return targetGrid.getCell(getCellMax(cell)); + } + + public Collection getIntersecting(Vector2i cell, Grid targetGrid) { + Vector2i min = getCellMin(cell, targetGrid); + Vector2i max = getCellMin(cell, targetGrid); + + if (min.equals(max)) return Collections.singleton(min); + + Collection intersects = new ArrayList<>(); + for (int x = min.getX(); x <= max.getX(); x++){ + for (int y = min.getY(); y <= max.getY(); y++){ + intersects.add(new Vector2i(x, y)); + } + } + + return intersects; + } + + public Grid multiply(Grid other) { + return new Grid( + this.gridSize.mul(other.gridSize), + this.offset.mul(other.gridSize).add(other.offset) + ); + } + + public Grid divide(Grid other) { + return new Grid( + this.gridSize.div(other.gridSize), + this.offset.sub(other.offset).div(other.gridSize) + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Grid grid = (Grid) o; + return gridSize.equals(grid.gridSize) && offset.equals(grid.offset); + } + + @Override + public int hashCode() { + return Objects.hash(gridSize, offset); + } + + @Override + public String toString() { + return "Grid{" + + "gridSize=" + gridSize + + ", offset=" + offset + + '}'; + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java similarity index 59% rename from BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java index 69842450..6d25551a 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java @@ -22,46 +22,36 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.common; - -import java.util.Objects; +package de.bluecolored.bluemap.core.world; import com.flowpowered.math.vector.Vector2i; -public class RenderTicket { - - private final MapType map; - private final Vector2i tile; - - public RenderTicket(MapType map, Vector2i tile) { - this.map = map; - this.tile = tile; +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +public interface Region { + + /** + * Returns a collection of all generated chunks.
+ * (Be aware that the collection is not cached and recollected each time from the world-files!) + */ + default Collection listChunks(){ + return listChunks(0); } - - public synchronized void render() { - map.renderTile(tile); + + /** + * Returns a collection of all chunks that have been modified at or after the specified timestamp.
+ * (Be aware that the collection is not cached and recollected each time from the world-files!) + */ + Collection listChunks(long modifiedSince); + + default Chunk loadChunk(int chunkX, int chunkZ) throws IOException { + return loadChunk(chunkX, chunkZ, false); } - - public MapType getMapType() { - return map; - } - - public Vector2i getTile() { - return tile; - } - - @Override - public int hashCode() { - return Objects.hash(map.getId(), tile); - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof RenderTicket)) return false; - RenderTicket ticket = (RenderTicket) other; - - if (!ticket.tile.equals(tile)) return false; - return ticket.map.getId().equals(map.getId()); - } - + + Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException; + + File getRegionFile(); + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java index cb41ba36..d3f4fa36 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java @@ -24,24 +24,22 @@ */ package de.bluecolored.bluemap.core.world; -import java.nio.file.Path; -import java.util.Collection; -import java.util.UUID; -import java.util.function.Predicate; - import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; -import de.bluecolored.bluemap.core.util.AABB; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; /** * A sliced view of a world. Everything outside the defined bounds is seen as "not generated" and "air". */ public class SlicedWorld implements World { - private World world; - private Vector3i min; - private Vector3i max; + private final World world; + private final Vector3i min; + private final Vector3i max; public SlicedWorld(World world, Vector3i min, Vector3i max) { this.world = world; @@ -75,18 +73,28 @@ public class SlicedWorld implements World { } @Override - public int getMaxY() { - return world.getMaxY(); + public int getMaxY(int x, int z) { + return world.getMaxY(x, z); } @Override - public int getMinY() { - return world.getMinY(); + public int getMinY(int x, int z) { + return world.getMinY(x, z); } - + @Override - public Biome getBiome(Vector3i pos) { - return world.getBiome(pos); + public Grid getChunkGrid() { + return world.getChunkGrid(); + } + + @Override + public Grid getRegionGrid() { + return world.getRegionGrid(); + } + + @Override + public Biome getBiome(int x, int y, int z) { + return world.getBiome(x, y, z); } @Override @@ -106,44 +114,29 @@ public class SlicedWorld implements World { block.setWorld(this); return block; } - + @Override - public Collection getChunkList(long modifiedSince, Predicate filter) { - return world.getChunkList(modifiedSince, filter.and(chunk -> isInside(chunk))); + public Chunk getChunk(int x, int z) { + return world.getChunk(x, z); } @Override - public boolean isChunkGenerated(Vector2i chunkPos) { - if (!isInside(chunkPos)) return false; - - return world.isChunkGenerated(chunkPos); + public Region getRegion(int x, int z) { + return world.getRegion(x, z); } - + @Override - public boolean isAreaGenerated(AABB area) { - return isAreaGenerated(area.getMin(), area.getMax()); - } - - @Override - public boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) { - return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax)); - } - - @Override - public boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) { - if (!isInside(chunkMin) && - !isInside(chunkMax) && - !isInside(new Vector2i(chunkMin.getX(), chunkMax.getY())) && - !isInside(new Vector2i(chunkMax.getX(), chunkMin.getY())) - ) return false; - - for (int x = chunkMin.getX(); x <= chunkMax.getX(); x++) { - for (int z = chunkMin.getY(); z <= chunkMax.getY(); z++) { - if (!world.isChunkGenerated(new Vector2i(x, z))) return false; - } + public Collection listRegions() { + Grid regionGrid = getRegionGrid(); + Collection regions = new ArrayList<>(); + + for (Vector2i region : world.listRegions()) { + Vector2i min = regionGrid.getCellMin(region); + Vector2i max = regionGrid.getCellMax(region); + if (isInside(min.getX(), min.getY()) || isInside(max.getX(), max.getY())) regions.add(region); } - - return true; + + return regions; } @Override @@ -152,8 +145,8 @@ public class SlicedWorld implements World { } @Override - public void invalidateChunkCache(Vector2i chunk) { - world.invalidateChunkCache(chunk); + public void invalidateChunkCache(int x, int z) { + world.invalidateChunkCache(x, z); } @Override @@ -161,15 +154,18 @@ public class SlicedWorld implements World { world.cleanUpChunkCache(); } - @Override - public Vector2i blockPosToChunkPos(Vector3i block) { - return world.blockPosToChunkPos(block); - } - private boolean isInside(Vector3i blockPos) { return isInside(blockPos.getX(), blockPos.getY(), blockPos.getZ()); } - + + private boolean isInside(int x, int z) { + return + x >= min.getX() && + x <= max.getX() && + z >= min.getZ() && + z <= max.getZ(); + } + private boolean isInside(int x, int y, int z) { return x >= min.getX() && @@ -180,19 +176,6 @@ public class SlicedWorld implements World { y <= max.getY(); } - private boolean isInside(Vector2i chunkPos) { - return isInside(chunkPos.getX(), chunkPos.getY()); - } - - private boolean isInside(int chunkX, int chunkZ) { - return - chunkX * 16 <= max.getX() && - chunkX * 16 + 15 >= min.getX() && - chunkZ * 16 <= max.getZ() && - chunkZ * 16 + 15 >= min.getZ(); - - } - private Block createAirBlock(Vector3i pos) { return new Block( this, diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java index 06080a63..fd7fd30a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java @@ -24,17 +24,14 @@ */ package de.bluecolored.bluemap.core.world; -import java.io.IOException; +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3i; + import java.nio.file.Path; import java.util.Collection; import java.util.UUID; import java.util.function.Predicate; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - -import de.bluecolored.bluemap.core.util.AABB; - /** * Represents a World on the Server
*
@@ -52,123 +49,60 @@ public interface World { Vector3i getSpawnPoint(); - default int getMaxY() { - return 255; - } + int getMaxY(int x, int z); - default int getMinY() { - return 0; - } + int getMinY(int x, int z); + + Grid getChunkGrid(); + + Grid getRegionGrid(); /** - * Returns the Biome on the specified position or the default biome if the block is not generated yet. + * Returns the {@link Biome} on the specified position or the default biome if the block is not generated yet. */ - Biome getBiome(Vector3i pos); + Biome getBiome(int x, int y, int z); /** - * Returns the Block on the specified position or an air-block if the block is not generated yet. + * Returns the {@link Block} on the specified position or an air-block if the block is not generated yet. */ Block getBlock(Vector3i pos); /** - * Returns the Block on the specified position or an air-block if the block is not generated yet. + * Returns the {@link Block} on the specified position or an air-block if the block is not generated yet. */ default Block getBlock(int x, int y, int z) { return getBlock(new Vector3i(x, y, z)); } - - /** - * Returns a collection of all generated chunks.
- * (Be aware that the collection is not cached and recollected each time from the world-files!) - */ - public default Collection getChunkList(){ - return getChunkList(0, c -> true); - } - - /** - * Returns a filtered collection of all generated chunks.
- * (Be aware that the collection is not cached and recollected each time from the world-files!) - */ - public default Collection getChunkList(Predicate filter){ - return getChunkList(0, filter); - } /** - * Returns a collection of all chunks that have been modified at or after the specified timestamp.
- * (Be aware that the collection is not cached and recollected each time from the world-files!) + * Returns the {@link Chunk} on the specified chunk-position */ - public default Collection getChunkList(long modifiedSince){ - return getChunkList(modifiedSince, c -> true); - } - - /** - * Returns a filtered collection of all chunks that have been modified at or after the specified timestamp.
- * (Be aware that the collection is not cached and recollected each time from the world-files!) - */ - public Collection getChunkList(long modifiedSince, Predicate filter); + Chunk getChunk(int x, int z); /** - * Returns true if and only if that chunk is fully generated and no world-generation or lighting has yet to be done. + * Returns the Chunk on the specified chunk-position */ - public boolean isChunkGenerated(Vector2i chunkPos); - - + Region getRegion(int x, int z); + /** - * Returns true if and only if all chunks the given area is intersecting are fully generated and no world-generation or lighting has yet to be done. - * @param area The area to check - * @throws IOException + * Returns a collection of all regions in this world. + * (Be aware that the collection is not cached and recollected each time from the world-files!) */ - public default boolean isAreaGenerated(AABB area) { - return isAreaGenerated(area.getMin(), area.getMax()); - } - - /** - * Returns true if and only if all chunks the given area is intersecting are fully generated and no world-generation or lighting has yet to be done. - * @param area The area to check - * @throws IOException - */ - public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) { - return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax)); - } - - /** - * Returns true if and only if all chunks in the given range are fully generated and no world-generation or lighting has yet to be done. - * @param area The area to check - * @throws IOException - */ - public default boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) { - for (int x = chunkMin.getX(); x <= chunkMax.getX(); x++) { - for (int z = chunkMin.getY(); z <= chunkMax.getY(); z++) { - if (!isChunkGenerated(new Vector2i(x, z))) return false; - } - } - - return true; - } - + Collection listRegions(); + /** * Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk */ - public void invalidateChunkCache(); + void invalidateChunkCache(); /** * Invalidates the chunk from the chunk-cache (if there is a cache), so that the chunk has to be reloaded from disk */ - public void invalidateChunkCache(Vector2i chunk); + void invalidateChunkCache(int x, int z); /** * Cleans up invalid cache-entries to free up memory */ - public void cleanUpChunkCache(); - - /** - * Returns the ChunkPosition for a BlockPosition - */ - public default Vector2i blockPosToChunkPos(Vector3i block) { - return new Vector2i( - block.getX() >> 4, - block.getZ() >> 4 - ); - } + void cleanUpChunkCache(); } diff --git a/BlueMapCore/src/main/resourceExtensions/mc1_12/assets/minecraft/models/block/bed/light_gray_foot.json b/BlueMapCore/src/main/resourceExtensions/mc1_12/assets/minecraft/models/block/bed/light_gray_foot.json index 573d7485..32229a2e 100644 --- a/BlueMapCore/src/main/resourceExtensions/mc1_12/assets/minecraft/models/block/bed/light_gray_foot.json +++ b/BlueMapCore/src/main/resourceExtensions/mc1_12/assets/minecraft/models/block/bed/light_gray_foot.json @@ -1,6 +1,6 @@ { "parent":"block/bed/bed_foot", "textures": { - "bed": "entity/bed/light_gray" + "bed": "entity/bed/silver" } } \ No newline at end of file diff --git a/BlueMapCore/src/main/resourceExtensions/mc1_12/assets/minecraft/models/block/bed/light_gray_head.json b/BlueMapCore/src/main/resourceExtensions/mc1_12/assets/minecraft/models/block/bed/light_gray_head.json index 9dafb5d9..c7c30c9c 100644 --- a/BlueMapCore/src/main/resourceExtensions/mc1_12/assets/minecraft/models/block/bed/light_gray_head.json +++ b/BlueMapCore/src/main/resourceExtensions/mc1_12/assets/minecraft/models/block/bed/light_gray_head.json @@ -1,6 +1,6 @@ { "parent":"block/bed/bed_head", "textures": { - "bed": "entity/bed/light_gray" + "bed": "entity/bed/silver" } } \ No newline at end of file diff --git a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/version.json b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/version.json index 553d0990..d1d2c538 100644 --- a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/version.json +++ b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/version.json @@ -1,3 +1,5 @@ { - "version": "${version}" + "version": "${version}", + "git-hash": "${gitHash}", + "git-clean": "${gitClean}" } \ No newline at end of file diff --git a/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/BlockStateTest.java b/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/BlockStateTest.java index e1232223..9a2d8b1a 100644 --- a/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/BlockStateTest.java +++ b/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/BlockStateTest.java @@ -24,10 +24,10 @@ */ package de.bluecolored.bluemap.core.world; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class BlockStateTest { diff --git a/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/GridTest.java b/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/GridTest.java new file mode 100644 index 00000000..e5cfad45 --- /dev/null +++ b/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/GridTest.java @@ -0,0 +1,136 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + +import com.flowpowered.math.vector.Vector2i; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GridTest { + + @Test + public void testGetCell() { + Grid grid = new Grid(16, 0); + assertEquals(new Vector2i(0, 0), grid.getCell(new Vector2i(0, 0))); + assertEquals(new Vector2i(0, 0), grid.getCell(new Vector2i(15, 2))); + assertEquals(new Vector2i(1, 1), grid.getCell(new Vector2i(16, 20))); + assertEquals(new Vector2i(-1, -1), grid.getCell(new Vector2i(-1, -16))); + + Grid grid2 = new Grid(16,2); + assertEquals(new Vector2i(-1, -1), grid2.getCell(new Vector2i(0, 0))); + assertEquals(new Vector2i(0, 0), grid2.getCell(new Vector2i(17, 2))); + } + + @Test + public void testCellMin() { + Grid grid = new Grid(16, 0); + assertEquals(new Vector2i(0, 0), grid.getCellMin(new Vector2i(0, 0))); + assertEquals(new Vector2i(16, 32), grid.getCellMin(new Vector2i(1, 2))); + assertEquals(new Vector2i(-32, -16), grid.getCellMin(new Vector2i(-2, -1))); + + Grid grid2 = new Grid(16, 2); + assertEquals(new Vector2i(2, 2), grid2.getCellMin(new Vector2i(0, 0))); + assertEquals(new Vector2i(18, 34), grid2.getCellMin(new Vector2i(1, 2))); + assertEquals(new Vector2i(-30, -14), grid2.getCellMin(new Vector2i(-2, -1))); + } + + @Test + public void testCellMax() { + Grid grid = new Grid(16, 0); + assertEquals(new Vector2i(15, 15), grid.getCellMax(new Vector2i(0, 0))); + assertEquals(new Vector2i(31, 47), grid.getCellMax(new Vector2i(1, 2))); + assertEquals(new Vector2i(-17, -1), grid.getCellMax(new Vector2i(-2, -1))); + + Grid grid2 = new Grid(16, 2); + assertEquals(new Vector2i(17, 17), grid2.getCellMax(new Vector2i(0, 0))); + assertEquals(new Vector2i(33, 49), grid2.getCellMax(new Vector2i(1, 2))); + assertEquals(new Vector2i(-15, 1), grid2.getCellMax(new Vector2i(-2, -1))); + } + + @Test + public void testCellMinWithSmallerTargetGrid() { + Grid grid = new Grid(16, 0); + Grid target = new Grid(2, 1); + + assertEquals(new Vector2i(-1, -1), grid.getCellMin(new Vector2i(0, 0), target)); + assertEquals(new Vector2i(-9, 7), grid.getCellMin(new Vector2i(-1, 1), target)); + } + + @Test + public void testCellMinWithBiggerTargetGrid() { + Grid grid = new Grid(2, 0); + Grid target = new Grid(8, 2); + + assertEquals(new Vector2i(-1, -1), grid.getCellMin(new Vector2i(0, 0), target)); + assertEquals(new Vector2i(-1, 1), grid.getCellMin(new Vector2i(-1, 8), target)); + assertEquals(new Vector2i(-1, 2), grid.getCellMin(new Vector2i(-1, 9), target)); + } + + @Test + public void testCellMaxWithSmallerTargetGrid() { + Grid grid = new Grid(16, 0); + Grid target = new Grid(2, 1); + + assertEquals(new Vector2i(7, 7), grid.getCellMax(new Vector2i(0, 0), target)); + assertEquals(new Vector2i(-1, 15), grid.getCellMax(new Vector2i(-1, 1), target)); + } + + @Test + public void testCellMaxWithBiggerTargetGrid() { + Grid grid = new Grid(2, 0); + Grid target = new Grid(8, 2); + + assertEquals(new Vector2i(-1, -1), grid.getCellMax(new Vector2i(0, 0), target)); + assertEquals(new Vector2i(-1, 1), grid.getCellMax(new Vector2i(-1, 8), target)); + assertEquals(new Vector2i(-1, 2), grid.getCellMax(new Vector2i(-1, 9), target)); + } + + @Test + public void testMultiply() { + Grid grid1 = new Grid(2, 5); + Grid grid2 = new Grid(4, 2); + + Grid result1 = new Grid(8, 22); + Grid result2 = new Grid(8, 9); + + assertEquals(result1, grid1.multiply(grid2)); + assertEquals(result2, grid2.multiply(grid1)); + } + + @Test + public void testDivide() { + Grid grid1 = new Grid(8, 22); + Grid grid2 = new Grid(4, 2); + Grid result1 = new Grid(2, 5); + assertEquals(result1, grid1.divide(grid2)); + + Grid grid3 = new Grid(8, 9); + Grid grid4 = new Grid(2, 5); + Grid result2 = new Grid(4, 2); + assertEquals(result2, grid3.divide(grid4)); + } + +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index a48a4f49..3900f523 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,9 @@ plugins { id 'java' - id 'com.github.johnrengelman.shadow' version '5.1.0' - id 'com.github.hierynomus.license' version '0.15.0' + id 'java-library' + id 'com.github.johnrengelman.shadow' version '6.1.0' + id 'com.github.hierynomus.license' version '0.16.1' + id 'com.palantir.git-version' version '0.12.3' } allprojects { @@ -36,6 +38,7 @@ allprojects { } apply plugin: 'java' + apply plugin: 'java-library' group = 'de.bluecolored.bluemap' version = coreVersion diff --git a/gradle.properties b/gradle.properties index 179f66c3..6af607ac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -coreVersion=1.4.2 +coreVersion=1.5.4 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5028f28f..0f80bbf5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/implementations/cli/build.gradle b/implementations/cli/build.gradle index d786106e..03b98dce 100644 --- a/implementations/cli/build.gradle +++ b/implementations/cli/build.gradle @@ -1,6 +1,6 @@ dependencies { - compile group: 'commons-cli', name: 'commons-cli', version: '1.4' - compile project(':BlueMapCommon') + implementation group: 'commons-cli', name: 'commons-cli', version: '1.4' + implementation project(':BlueMapCommon') } jar { @@ -10,14 +10,14 @@ jar { } build.dependsOn shadowJar { - destinationDir = file '../../build/release' - archiveFileName = "BlueMap-${version}-cli.jar" + destinationDirectory = file '../../build/release' + archiveFileName.set("BlueMap-${archiveVersion.get()}-cli.jar") //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it relocate 'com.google', 'de.bluecolored.shadow.google' relocate 'com.typesafe', 'de.bluecolored.shadow.typesafe' relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'org.apache', 'de.bluecolored.shadow.apache' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' @@ -26,4 +26,5 @@ build.dependsOn shadowJar { relocate 'com.mojang.brigadier', 'de.bluecolored.shadow.mojang.brigadier' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } \ No newline at end of file diff --git a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index afd4dbac..ee785bef 100644 --- a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -24,196 +24,145 @@ */ package de.bluecolored.bluemap.cli; -import com.flowpowered.math.GenericMath; -import com.flowpowered.math.vector.Vector2i; -import de.bluecolored.bluemap.common.*; +import de.bluecolored.bluemap.common.BlueMapService; +import de.bluecolored.bluemap.common.MissingResourcesException; +import de.bluecolored.bluemap.common.plugin.RegionFileWatchService; +import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; +import de.bluecolored.bluemap.common.rendermanager.RenderManager; +import de.bluecolored.bluemap.common.rendermanager.RenderTask; +import de.bluecolored.bluemap.common.web.FileRequestHandler; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.config.WebServerConfig; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.LoggerLogger; +import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.metrics.Metrics; -import de.bluecolored.bluemap.core.render.hires.HiresModelManager; import de.bluecolored.bluemap.core.util.FileUtils; -import de.bluecolored.bluemap.core.web.FileRequestHandler; -import de.bluecolored.bluemap.core.web.WebSettings; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; import de.bluecolored.bluemap.core.webserver.WebServer; -import de.bluecolored.bluemap.core.world.World; import org.apache.commons.cli.*; import org.apache.commons.lang3.time.DurationFormatUtils; -import java.io.*; -import java.util.Collection; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; public class BlueMapCLI { - public void renderMaps(BlueMapService blueMap, boolean forceRender, boolean forceGenerateWebapp) throws IOException, InterruptedException { + public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRender, boolean forceGenerateWebapp) throws IOException, InterruptedException { //metrics report if (blueMap.getCoreConfig().isMetricsEnabled()) Metrics.sendReportAsync("cli"); blueMap.createOrUpdateWebApp(forceGenerateWebapp); - WebSettings webSettings = blueMap.updateWebAppSettings(); - - RenderManager renderManager = new RenderManager(blueMap.getCoreConfig().getRenderThreadCount()); - File rmstate = new File(blueMap.getCoreConfig().getDataFolder(), "rmstate"); - - if (!forceRender && rmstate.exists()) { - try ( - InputStream in = new GZIPInputStream(new FileInputStream(rmstate)); - DataInputStream din = new DataInputStream(in); - ){ - renderManager.readState(din, blueMap.getMaps().values()); - Logger.global.logInfo("Found unfinished render, continuing ... (If you want to start a new render, delete the this file: " + rmstate.getCanonicalPath() + " or force a full render using -f)"); - } catch (IOException ex) { - Logger.global.logError("Failed to read saved render-state! Remove the file " + rmstate.getCanonicalPath() + " to start a new render.", ex); - return; - } - } else { - for (MapType map : blueMap.getMaps().values()) { - Logger.global.logInfo("Creating render-task for map '" + map.getId() + "' ..."); - Logger.global.logInfo("Collecting tiles ..."); - - Collection chunks; - if (!forceRender) { - long lastRender = webSettings.getLong("maps", 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; - } + blueMap.updateWebAppSettings(); - Vector2i renderCenter = map.getWorld().getSpawnPoint().toVector2(true); - - RenderTask task = new RenderTask(map.getId(), map); - task.addTiles(tiles); - task.optimizeQueue(renderCenter); - - renderManager.addRenderTask(task); - } - } + //try load resources + blueMap.getResourcePack(); - Logger.global.logInfo("Starting render ..."); - renderManager.start(); - - Thread shutdownHook = new Thread(() -> { - Logger.global.logInfo("Stopping render ..."); - renderManager.stop(); - - Logger.global.logInfo("Saving tiles ..."); - RenderTask currentTask = renderManager.getCurrentRenderTask(); - if (currentTask != null){ - currentTask.getMapType().getTileRenderer().save(); - } + //create renderManager + RenderManager renderManager = new RenderManager(); - try { - Logger.global.logInfo("Saving render-state ..."); - FileUtils.createFile(rmstate); + //load maps + Map maps = blueMap.getMaps(); - try ( - OutputStream os = new GZIPOutputStream(new FileOutputStream(rmstate)); - DataOutputStream dos = new DataOutputStream(os); - ) { - renderManager.writeState(dos); - - Logger.global.logInfo("Render saved and stopped! Restart the render (without using -f) to resume."); - } - } catch (IOException ex) { - Logger.global.logError("Failed to save render-state!", ex); - } - }); - Runtime.getRuntime().addShutdownHook(shutdownHook); - - long startTime = System.currentTimeMillis(); - - long lastLogUpdate = startTime; - long lastSave = startTime; - - while(renderManager.getRenderTaskCount() != 0) { - try { - Thread.sleep(200); - } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } - - - long now = System.currentTimeMillis(); - - if (lastLogUpdate < now - 10000) { // print update all 10 seconds - RenderTask currentTask = renderManager.getCurrentRenderTask(); - if (currentTask == null) continue; - - 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(); - if (currentTask == null) continue; - - lastSave = now; - currentTask.getMapType().getTileRenderer().save(); - - try ( - OutputStream os = new GZIPOutputStream(new FileOutputStream(rmstate)); - DataOutputStream dos = new DataOutputStream(os); - ){ - renderManager.writeState(dos); + //watcher + List regionFileWatchServices = new ArrayList<>(); + if (watch) { + for (BmMap map : maps.values()) { + try { + RegionFileWatchService watcher = new RegionFileWatchService(renderManager, map, true); + watcher.start(); + regionFileWatchServices.add(watcher); } catch (IOException ex) { - Logger.global.logError("Failed to save render-state!", ex); - } - - //clean up caches - for (World world : blueMap.getWorlds().values()) { - world.cleanUpChunkCache(); + Logger.global.logError("Failed to create file-watcher for map: " + map.getId() + + " (This map might not automatically update)", ex); } } } - //render finished and saved, so this is no longer needed - Runtime.getRuntime().removeShutdownHook(shutdownHook); - - //stop render-threads - renderManager.stop(); - - //render finished, so remove render state file - FileUtils.delete(rmstate); - - for (MapType map : blueMap.getMaps().values()) { - webSettings.set(startTime, "maps", map.getId(), "last-render"); - } - - try { - webSettings.save(); - } catch (IOException e) { - Logger.global.logError("Failed to update web-settings!", e); + //update all maps + int totalRegions = 0; + for (BmMap map : maps.values()) { + MapUpdateTask updateTask = new MapUpdateTask(map, forceRender); + renderManager.scheduleRenderTask(updateTask); + totalRegions += updateTask.getRegions().size(); } - Logger.global.logInfo("Render finished!"); + Logger.global.logInfo("Start updating " + maps.size() + " maps (" + totalRegions + " regions, ~" + totalRegions * 1024L + " chunks)..."); + + // start rendering + renderManager.start(blueMap.getCoreConfig().getRenderThreadCount()); + + Timer timer = new Timer("BlueMap-CLI-Timer", true); + TimerTask updateInfoTask = new TimerTask() { + @Override + public void run() { + RenderTask task = renderManager.getCurrentRenderTask(); + if (task == null) return; + + double progress = task.estimateProgress(); + long etaMs = renderManager.estimateCurrentRenderTaskTimeRemaining(); + + String eta = ""; + if (etaMs > 0) { + String etrDurationString = DurationFormatUtils.formatDuration(etaMs, "HH:mm:ss"); + eta = " (ETA: " + etrDurationString + ")"; + } + Logger.global.logInfo(task.getDescription() + ": " + (Math.round(progress * 100000) / 1000.0) + "%" + eta); + } + }; + timer.scheduleAtFixedRate(updateInfoTask, TimeUnit.SECONDS.toMillis(10), TimeUnit.SECONDS.toMillis(10)); + + TimerTask saveTask = new TimerTask() { + @Override + public void run() { + for (BmMap map : maps.values()) { + map.save(); + } + } + }; + timer.scheduleAtFixedRate(saveTask, TimeUnit.MINUTES.toMillis(2), TimeUnit.MINUTES.toMillis(2)); + + Runnable shutdown = () -> { + Logger.global.logInfo("Stopping..."); + updateInfoTask.cancel(); + saveTask.cancel(); + renderManager.stop(); + + for (RegionFileWatchService watcher : regionFileWatchServices) { + watcher.close(); + } + regionFileWatchServices.clear(); + + try { + renderManager.awaitShutdown(); + } catch (InterruptedException e) { + Logger.global.logError("Unexpected interruption: ", e); + } + + Logger.global.logInfo("Saving..."); + saveTask.run(); + + Logger.global.logInfo("Stopped."); + }; + + Thread shutdownHook = new Thread(shutdown); + Runtime.getRuntime().addShutdownHook(shutdownHook); + + // wait until done, then shutdown if not watching + renderManager.awaitIdle(); + Logger.global.logInfo("Your maps are now all up-to-date!"); + + if (watch) { + updateInfoTask.cancel(); + Logger.global.logInfo("Waiting for changes on the world-files..."); + } else { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + shutdown.run(); + } } public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOException { @@ -261,11 +210,11 @@ public class BlueMapCLI { } //minecraft version - MinecraftVersion version = MinecraftVersion.getLatest(); + MinecraftVersion version = MinecraftVersion.LATEST_SUPPORTED; if (cmd.hasOption("v")) { String versionString = cmd.getOptionValue("v"); try { - version = MinecraftVersion.fromVersionString(versionString); + version = MinecraftVersion.of(versionString); } catch (IllegalArgumentException e) { Logger.global.logWarning("Could not determine a version from the provided version-string: '" + versionString + "'"); System.exit(1); @@ -285,9 +234,11 @@ public class BlueMapCLI { if (cmd.hasOption("r")) { noActions = false; - + + boolean watch = cmd.hasOption("u"); boolean force = cmd.hasOption("f"); - cli.renderMaps(blueMap, force, cmd.hasOption("g")); + boolean generateWebappFiles = cmd.hasOption("g"); + cli.renderMaps(blueMap, watch, force, generateWebappFiles); } else { if (cmd.hasOption("g")) { noActions = false; @@ -307,7 +258,7 @@ public class BlueMapCLI { !blueMap.getRenderConfigFile().exists() || !blueMap.getWebServerConfigFile().exists() ) { - Logger.global.logInfo("Generating default config files for you, here: " + configFolder.getCanonicalPath().toString() + "\n"); + Logger.global.logInfo("Generating default config files for you, here: " + configFolder.getCanonicalPath() + "\n"); } //generate all configs @@ -321,31 +272,25 @@ public class BlueMapCLI { //print help BlueMapCLI.printHelp(); System.exit(1); - return; } } catch (MissingResourcesException e) { Logger.global.logWarning("BlueMap is missing important resources!"); Logger.global.logWarning("You must accept the required file download in order for BlueMap to work!"); - try { Logger.global.logWarning("Please check: " + blueMap.getCoreConfigFile().getCanonicalPath()); } catch (NullPointerException | IOException ignored) {} + try { if (blueMap != null) Logger.global.logWarning("Please check: " + blueMap.getCoreConfigFile().getCanonicalPath()); } catch (IOException ignored) {} System.exit(2); - return; } catch (ParseException e) { Logger.global.logError("Failed to parse provided arguments!", e); BlueMapCLI.printHelp(); System.exit(1); - return; } catch (IOException e) { Logger.global.logError("An IO-error occurred!", e); System.exit(1); - return; } catch (InterruptedException ex) { System.exit(1); - return; } catch (RuntimeException e) { Logger.global.logError("An unexpected error occurred!", e); System.exit(1); - return; } } @@ -390,6 +335,8 @@ public class BlueMapCLI { options.addOption("r", "render", false, "Renders the maps configured in the 'render.conf' file"); options.addOption("f", "force-render", false, "Forces rendering everything, instead of only rendering chunks that have been modified since the last render"); + + options.addOption("u", "watch", false, "Watches for file-changes after rendering and updates the map"); return options; } @@ -414,18 +361,19 @@ public class BlueMapCLI { } catch (IOException ignore) {} String command = "java -jar " + filename; - + + @SuppressWarnings("StringBufferReplaceableByString") StringBuilder footer = new StringBuilder(); footer.append("Examples:\n\n"); - footer.append(command + " -c './config/'\n"); + footer.append(command).append(" -c './config/'\n"); footer.append("Generates the default/example configurations in a folder named 'config' if they are not already present\n\n"); - footer.append(command + " -r\n"); + footer.append(command).append(" -r\n"); footer.append("Render the configured maps\n\n"); - footer.append(command + " -w\n"); + footer.append(command).append(" -w\n"); footer.append("Start only the webserver without doing anything else\n\n"); - footer.append(command + " -gs\n"); + footer.append(command).append(" -gs\n"); footer.append("Generate the web-app and settings without starting a render\n\n"); - + formatter.printHelp(command + " [options]", "\nOptions:", createOptions(), "\n" + footer.toString()); } diff --git a/implementations/cli/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/cli/src/main/resources/de/bluecolored/bluemap/render-defaults.conf index 47620ad1..7f183f02 100644 --- a/implementations/cli/src/main/resources/de/bluecolored/bluemap/render-defaults.conf +++ b/implementations/cli/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -1,3 +1,4 @@ webroot: "web" useCookies: true +enableFreeFlight: true maps: [] diff --git a/implementations/cli/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/cli/src/main/resources/de/bluecolored/bluemap/render.conf index a320afc7..e90b856f 100644 --- a/implementations/cli/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/cli/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/fabric-1.15.2/build.gradle b/implementations/fabric-1.15.2/build.gradle index 58604250..9ce194ed 100644 --- a/implementations/fabric-1.15.2/build.gradle +++ b/implementations/fabric-1.15.2/build.gradle @@ -1,11 +1,11 @@ import net.fabricmc.loom.task.RemapJarTask plugins { - id 'fabric-loom' version '0.4-SNAPSHOT' + id 'fabric-loom' version '0.8-SNAPSHOT' } configurations { - compile.extendsFrom shadowInclude + implementation.extendsFrom shadowInclude } dependencies { @@ -27,14 +27,9 @@ dependencies { processResources { inputs.property "version", project.version - from(sourceSets.main.resources.srcDirs) { - include "fabric.mod.json" + filesMatching("fabric.mod.json") { expand "version": project.version } - - from(sourceSets.main.resources.srcDirs) { - exclude "fabric.mod.json" - } } shadowJar { @@ -43,7 +38,7 @@ shadowJar { //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' @@ -51,18 +46,19 @@ shadowJar { relocate 'com.google.inject', 'de.bluecolored.shadow.google.inject' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } task ramappedShadowJar(type: RemapJarTask) { - destinationDir = file '../../build/release' + destinationDirectory = file '../../build/release' dependsOn tasks.shadowJar - input = tasks.shadowJar.archivePath - addNestedDependencies = true - archiveName = "BlueMap-${version}-fabric-1.15.2.jar" + input.set(tasks.shadowJar.archiveFile.get()) + addNestedDependencies.set(true) + archiveFileName.set("BlueMap-${archiveVersion.get()}-fabric-1.15.2.jar") } build.dependsOn ramappedShadowJar task sourcesJar(type: Jar, dependsOn: classes) { - classifier = "sources" + archiveClassifier.set("sources") from sourceSets.main.allSource } diff --git a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java b/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java index c96846e6..460a6c15 100644 --- a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java +++ b/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java @@ -24,47 +24,25 @@ */ package de.bluecolored.bluemap.fabric; -import java.io.IOException; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; +import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; + import java.util.ArrayList; import java.util.Collection; import java.util.UUID; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback; -import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; -import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; -import de.bluecolored.bluemap.fabric.events.WorldSaveCallback; -import net.fabricmc.fabric.api.event.player.AttackBlockCallback; -import net.fabricmc.fabric.api.event.player.UseBlockCallback; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; -import net.minecraft.world.World; - public class FabricEventForwarder { - private FabricMod mod; - private Collection eventListeners; + private final FabricMod mod; + private final Collection eventListeners; public FabricEventForwarder(FabricMod mod) { this.mod = mod; this.eventListeners = new ArrayList<>(1); - WorldSaveCallback.EVENT.register(this::onWorldSave); - ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize); - AttackBlockCallback.EVENT.register(this::onBlockAttack); - UseBlockCallback.EVENT.register(this::onBlockUse); - PlayerJoinCallback.EVENT.register(this::onPlayerJoin); PlayerLeaveCallback.EVENT.register(this::onPlayerLeave); } @@ -76,53 +54,7 @@ public class FabricEventForwarder { public synchronized void removeAllListeners() { this.eventListeners.clear(); } - - public ActionResult onBlockUse(PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) { - if (world instanceof ServerWorld) { - onBlockChange((ServerWorld) world, hitResult.getBlockPos()); - } - - return ActionResult.PASS; - } - - public ActionResult onBlockAttack(PlayerEntity player, World world, Hand hand, BlockPos pos, Direction direction) { - if (world instanceof ServerWorld) { - onBlockChange((ServerWorld) world, pos); - } - - return ActionResult.PASS; - } - - public synchronized void onBlockChange(ServerWorld world, BlockPos blockPos) { - Vector3i position = new Vector3i(blockPos.getX(), blockPos.getY(), blockPos.getZ()); - - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onBlockChange(uuid, position); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - public synchronized void onWorldSave(ServerWorld world) { - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(uuid); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - public synchronized void onChunkFinalize(ServerWorld world, Vector2i chunkPos) { - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(uuid, chunkPos); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) { if (this.mod.getServer() != server) return; diff --git a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java b/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java index 95333919..88599c57 100644 --- a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java +++ b/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java @@ -24,24 +24,8 @@ */ package de.bluecolored.bluemap.fabric; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import org.apache.logging.log4j.LogManager; - import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; - import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.plugin.serverinterface.Player; @@ -60,6 +44,14 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; +import org.apache.logging.log4j.LogManager; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; public class FabricMod implements ModInitializer, ServerInterface { @@ -81,7 +73,7 @@ public class FabricMod implements ModInitializer, ServerInterface { this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); - pluginInstance = new Plugin(MinecraftVersion.MC_1_15, "fabric-1.15.2", this); + pluginInstance = new Plugin(new MinecraftVersion(1, 15, 2), "fabric-1.15.2", this); this.worldUUIDs = new ConcurrentHashMap<>(); this.eventForwarder = new FabricEventForwarder(this); diff --git a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java b/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java deleted file mode 100644 index efbb5320..00000000 --- a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.fabric.mixin; - -import java.util.concurrent.CompletableFuture; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import com.flowpowered.math.vector.Vector2i; -import com.mojang.datafixers.util.Either; - -import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback; -import net.minecraft.server.world.ChunkHolder; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.server.world.ThreadedAnvilChunkStorage; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkStatus; - -@Mixin(ThreadedAnvilChunkStorage.class) -public abstract class MixinThreadedAnvilChunkStorage { - - @Accessor("world") - public abstract ServerWorld getWorld(); - - @Inject(at = @At("RETURN"), method = "method_20617") - public void upgradeChunk(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable>> ci) { - if (requiredStatus == ChunkStatus.FULL) { - ChunkFinalizeCallback.EVENT.invoker().onChunkFinalized(getWorld(), new Vector2i(holder.getPos().x, holder.getPos().z)); - } - } - -} diff --git a/implementations/fabric-1.15.2/src/main/resources/bluemap.mixins.json b/implementations/fabric-1.15.2/src/main/resources/bluemap.mixins.json index 973877f8..2bfdbf9e 100644 --- a/implementations/fabric-1.15.2/src/main/resources/bluemap.mixins.json +++ b/implementations/fabric-1.15.2/src/main/resources/bluemap.mixins.json @@ -6,9 +6,7 @@ "mixins": [], "client": [], "server": [ - "MixinThreadedAnvilChunkStorage", - "MixinPlayerManager", - "MixinServerWorld" + "MixinPlayerManager" ], "injectors": { "defaultRequire": 1 diff --git a/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf index ef3bb021..74d53aa7 100644 --- a/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf +++ b/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -2,4 +2,5 @@ liveUpdates: true skinDownload: true hiddenGameModes: [] hideInvisible: true -hideSneaking: false \ No newline at end of file +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin.conf index 67f297e2..2d3ec7e3 100644 --- a/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin.conf +++ b/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -24,4 +24,9 @@ hideInvisible: true # If this is true, players that are sneaking will be hidden on the map. # Default is false -hideSneaking: false \ No newline at end of file +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf index 1efd4978..4a602264 100644 --- a/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/fabric-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "bluemap/web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/fabric-1.16.1/build.gradle b/implementations/fabric-1.16.1/build.gradle index 7f4ad7f8..1f082798 100644 --- a/implementations/fabric-1.16.1/build.gradle +++ b/implementations/fabric-1.16.1/build.gradle @@ -1,11 +1,11 @@ import net.fabricmc.loom.task.RemapJarTask plugins { - id 'fabric-loom' version '0.4-SNAPSHOT' + id 'fabric-loom' version '0.8-SNAPSHOT' } configurations { - compile.extendsFrom shadowInclude + implementation.extendsFrom shadowInclude } dependencies { @@ -27,14 +27,9 @@ dependencies { processResources { inputs.property "version", project.version - from(sourceSets.main.resources.srcDirs) { - include "fabric.mod.json" + filesMatching("fabric.mod.json") { expand "version": project.version } - - from(sourceSets.main.resources.srcDirs) { - exclude "fabric.mod.json" - } } shadowJar { @@ -43,7 +38,7 @@ shadowJar { //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' @@ -51,18 +46,19 @@ shadowJar { relocate 'com.google.inject', 'de.bluecolored.shadow.google.inject' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } task ramappedShadowJar(type: RemapJarTask) { - destinationDir = file '../../build/release' + destinationDirectory = file '../../build/release' dependsOn tasks.shadowJar - input = tasks.shadowJar.archivePath - addNestedDependencies = true - archiveName = "BlueMap-${version}-fabric-1.16.1.jar" + input.set(tasks.shadowJar.archiveFile.get()) + addNestedDependencies.set(true) + archiveFileName.set("BlueMap-${archiveVersion.get()}-fabric-1.16.1.jar") } build.dependsOn ramappedShadowJar task sourcesJar(type: Jar, dependsOn: classes) { - classifier = "sources" + archiveClassifier.set("sources") from sourceSets.main.allSource } diff --git a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java b/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java index c96846e6..4ef3a4e4 100644 --- a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java +++ b/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java @@ -24,47 +24,25 @@ */ package de.bluecolored.bluemap.fabric; -import java.io.IOException; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; +import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; + import java.util.ArrayList; import java.util.Collection; import java.util.UUID; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback; -import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; -import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; -import de.bluecolored.bluemap.fabric.events.WorldSaveCallback; -import net.fabricmc.fabric.api.event.player.AttackBlockCallback; -import net.fabricmc.fabric.api.event.player.UseBlockCallback; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; -import net.minecraft.world.World; - public class FabricEventForwarder { - private FabricMod mod; - private Collection eventListeners; + private final FabricMod mod; + private final Collection eventListeners; public FabricEventForwarder(FabricMod mod) { this.mod = mod; this.eventListeners = new ArrayList<>(1); - - WorldSaveCallback.EVENT.register(this::onWorldSave); - ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize); - AttackBlockCallback.EVENT.register(this::onBlockAttack); - UseBlockCallback.EVENT.register(this::onBlockUse); - + PlayerJoinCallback.EVENT.register(this::onPlayerJoin); PlayerLeaveCallback.EVENT.register(this::onPlayerLeave); } @@ -77,52 +55,6 @@ public class FabricEventForwarder { this.eventListeners.clear(); } - - public ActionResult onBlockUse(PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) { - if (world instanceof ServerWorld) { - onBlockChange((ServerWorld) world, hitResult.getBlockPos()); - } - - return ActionResult.PASS; - } - - public ActionResult onBlockAttack(PlayerEntity player, World world, Hand hand, BlockPos pos, Direction direction) { - if (world instanceof ServerWorld) { - onBlockChange((ServerWorld) world, pos); - } - - return ActionResult.PASS; - } - - public synchronized void onBlockChange(ServerWorld world, BlockPos blockPos) { - Vector3i position = new Vector3i(blockPos.getX(), blockPos.getY(), blockPos.getZ()); - - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onBlockChange(uuid, position); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - public synchronized void onWorldSave(ServerWorld world) { - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(uuid); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - public synchronized void onChunkFinalize(ServerWorld world, Vector2i chunkPos) { - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(uuid, chunkPos); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) { if (this.mod.getServer() != server) return; diff --git a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java b/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java index a9b49116..e61a27dd 100644 --- a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java +++ b/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java @@ -24,24 +24,8 @@ */ package de.bluecolored.bluemap.fabric; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import org.apache.logging.log4j.LogManager; - import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; - import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.plugin.serverinterface.Player; @@ -62,6 +46,14 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.WorldSavePath; import net.minecraft.world.dimension.DimensionType; +import org.apache.logging.log4j.LogManager; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; public class FabricMod implements ModInitializer, ServerInterface { @@ -83,7 +75,7 @@ public class FabricMod implements ModInitializer, ServerInterface { this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); - pluginInstance = new Plugin(MinecraftVersion.MC_1_16, "fabric-1.16.1", this); + pluginInstance = new Plugin(new MinecraftVersion(1, 16, 1), "fabric-1.16.1", this); this.worldUUIDs = new ConcurrentHashMap<>(); this.eventForwarder = new FabricEventForwarder(this); diff --git a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java b/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java deleted file mode 100644 index d2647bfa..00000000 --- a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.fabric.mixin; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import de.bluecolored.bluemap.fabric.events.WorldSaveCallback; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.ProgressListener; - -@Mixin(ServerWorld.class) -public abstract class MixinServerWorld { - - @Inject(at = @At("RETURN"), method = "save") - public void save(ProgressListener progressListener, boolean flush, boolean bl, CallbackInfo ci) { - WorldSaveCallback.EVENT.invoker().onWorldSaved((ServerWorld) (Object) this); - } - -} diff --git a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java b/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java deleted file mode 100644 index 4ffcef51..00000000 --- a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.fabric.mixin; - -import java.util.concurrent.CompletableFuture; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import com.flowpowered.math.vector.Vector2i; -import com.mojang.datafixers.util.Either; - -import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback; -import net.minecraft.server.world.ChunkHolder; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.server.world.ThreadedAnvilChunkStorage; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkStatus; - -@Mixin(ThreadedAnvilChunkStorage.class) -public abstract class MixinThreadedAnvilChunkStorage { - - @Accessor("world") - public abstract ServerWorld getWorld(); - - @Inject(at = @At("RETURN"), method = "generateChunk") - public void upgradeChunk(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable>> ci) { - if (requiredStatus == ChunkStatus.FULL) { - ChunkFinalizeCallback.EVENT.invoker().onChunkFinalized(getWorld(), new Vector2i(holder.getPos().x, holder.getPos().z)); - } - } - -} diff --git a/implementations/fabric-1.16.1/src/main/resources/bluemap.mixins.json b/implementations/fabric-1.16.1/src/main/resources/bluemap.mixins.json index 973877f8..2bfdbf9e 100644 --- a/implementations/fabric-1.16.1/src/main/resources/bluemap.mixins.json +++ b/implementations/fabric-1.16.1/src/main/resources/bluemap.mixins.json @@ -6,9 +6,7 @@ "mixins": [], "client": [], "server": [ - "MixinThreadedAnvilChunkStorage", - "MixinPlayerManager", - "MixinServerWorld" + "MixinPlayerManager" ], "injectors": { "defaultRequire": 1 diff --git a/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf index ef3bb021..74d53aa7 100644 --- a/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf +++ b/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -2,4 +2,5 @@ liveUpdates: true skinDownload: true hiddenGameModes: [] hideInvisible: true -hideSneaking: false \ No newline at end of file +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/plugin.conf index 67f297e2..2d3ec7e3 100644 --- a/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/plugin.conf +++ b/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -24,4 +24,9 @@ hideInvisible: true # If this is true, players that are sneaking will be hidden on the map. # Default is false -hideSneaking: false \ No newline at end of file +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/render-defaults.conf index 2f276c92..3b775ae3 100644 --- a/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/render-defaults.conf +++ b/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -1,3 +1,4 @@ webroot: "bluemap/web" useCookies: true +enableFreeFlight: true maps: [] diff --git a/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/render.conf index 1efd4978..4a602264 100644 --- a/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/fabric-1.16.1/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "bluemap/web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/fabric-1.16.2/build.gradle b/implementations/fabric-1.16.2/build.gradle index 9a800f76..f20c9a36 100644 --- a/implementations/fabric-1.16.2/build.gradle +++ b/implementations/fabric-1.16.2/build.gradle @@ -1,11 +1,11 @@ import net.fabricmc.loom.task.RemapJarTask plugins { - id 'fabric-loom' version '0.4-SNAPSHOT' + id 'fabric-loom' version '0.8-SNAPSHOT' } configurations { - compile.extendsFrom shadowInclude + implementation.extendsFrom shadowInclude } dependencies { @@ -27,14 +27,9 @@ dependencies { processResources { inputs.property "version", project.version - from(sourceSets.main.resources.srcDirs) { - include "fabric.mod.json" + filesMatching("fabric.mod.json") { expand "version": project.version } - - from(sourceSets.main.resources.srcDirs) { - exclude "fabric.mod.json" - } } shadowJar { @@ -43,7 +38,7 @@ shadowJar { //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' @@ -51,18 +46,19 @@ shadowJar { relocate 'com.google.inject', 'de.bluecolored.shadow.google.inject' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } task ramappedShadowJar(type: RemapJarTask) { - destinationDir = file '../../build/release' + destinationDirectory = file '../../build/release' dependsOn tasks.shadowJar - input = tasks.shadowJar.archivePath - addNestedDependencies = true - archiveName = "BlueMap-${version}-fabric-1.16.4.jar" + input.set(tasks.shadowJar.archiveFile.get()) + addNestedDependencies.set(true) + archiveFileName.set("BlueMap-${archiveVersion.get()}-fabric-1.16.4.jar") } build.dependsOn ramappedShadowJar task sourcesJar(type: Jar, dependsOn: classes) { - classifier = "sources" + archiveClassifier.set("sources") from sourceSets.main.allSource } diff --git a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java b/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java index c96846e6..ade2b042 100644 --- a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java +++ b/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java @@ -24,33 +24,16 @@ */ package de.bluecolored.bluemap.fabric; -import java.io.IOException; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; +import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; + import java.util.ArrayList; import java.util.Collection; import java.util.UUID; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback; -import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; -import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; -import de.bluecolored.bluemap.fabric.events.WorldSaveCallback; -import net.fabricmc.fabric.api.event.player.AttackBlockCallback; -import net.fabricmc.fabric.api.event.player.UseBlockCallback; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; -import net.minecraft.world.World; - public class FabricEventForwarder { private FabricMod mod; @@ -60,11 +43,6 @@ public class FabricEventForwarder { this.mod = mod; this.eventListeners = new ArrayList<>(1); - WorldSaveCallback.EVENT.register(this::onWorldSave); - ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize); - AttackBlockCallback.EVENT.register(this::onBlockAttack); - UseBlockCallback.EVENT.register(this::onBlockUse); - PlayerJoinCallback.EVENT.register(this::onPlayerJoin); PlayerLeaveCallback.EVENT.register(this::onPlayerLeave); } @@ -77,52 +55,6 @@ public class FabricEventForwarder { this.eventListeners.clear(); } - - public ActionResult onBlockUse(PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) { - if (world instanceof ServerWorld) { - onBlockChange((ServerWorld) world, hitResult.getBlockPos()); - } - - return ActionResult.PASS; - } - - public ActionResult onBlockAttack(PlayerEntity player, World world, Hand hand, BlockPos pos, Direction direction) { - if (world instanceof ServerWorld) { - onBlockChange((ServerWorld) world, pos); - } - - return ActionResult.PASS; - } - - public synchronized void onBlockChange(ServerWorld world, BlockPos blockPos) { - Vector3i position = new Vector3i(blockPos.getX(), blockPos.getY(), blockPos.getZ()); - - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onBlockChange(uuid, position); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - public synchronized void onWorldSave(ServerWorld world) { - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(uuid); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - public synchronized void onChunkFinalize(ServerWorld world, Vector2i chunkPos) { - try { - UUID uuid = mod.getUUIDForWorld(world); - for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(uuid, chunkPos); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) { if (this.mod.getServer() != server) return; diff --git a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java b/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java index 7918afea..97674810 100644 --- a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java +++ b/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java @@ -24,24 +24,8 @@ */ package de.bluecolored.bluemap.fabric; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import org.apache.logging.log4j.LogManager; - import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; - import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.plugin.serverinterface.Player; @@ -62,6 +46,14 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.WorldSavePath; import net.minecraft.world.dimension.DimensionType; +import org.apache.logging.log4j.LogManager; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; public class FabricMod implements ModInitializer, ServerInterface { @@ -83,7 +75,7 @@ public class FabricMod implements ModInitializer, ServerInterface { this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); - pluginInstance = new Plugin(MinecraftVersion.MC_1_16, "fabric-1.16.2", this); + pluginInstance = new Plugin(new MinecraftVersion(1, 16, 2), "fabric-1.16.2", this); this.worldUUIDs = new ConcurrentHashMap<>(); this.eventForwarder = new FabricEventForwarder(this); diff --git a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java b/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java deleted file mode 100644 index 49de240d..00000000 --- a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.fabric.events; - -import com.flowpowered.math.vector.Vector2i; - -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.server.world.ServerWorld; - -public interface ChunkFinalizeCallback { - Event EVENT = EventFactory.createArrayBacked(ChunkFinalizeCallback.class, - (listeners) -> (world, chunkPos) -> { - for (ChunkFinalizeCallback event : listeners) { - event.onChunkFinalized(world, chunkPos); - } - } - ); - - void onChunkFinalized(ServerWorld world, Vector2i chunkPos); -} diff --git a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java b/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java deleted file mode 100644 index 24c83dfa..00000000 --- a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinThreadedAnvilChunkStorage.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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.fabric.mixin; - -import java.util.concurrent.CompletableFuture; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import com.flowpowered.math.vector.Vector2i; -import com.mojang.datafixers.util.Either; - -import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback; -import net.minecraft.server.world.ChunkHolder; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.server.world.ThreadedAnvilChunkStorage; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkStatus; - -@Mixin(ThreadedAnvilChunkStorage.class) -public abstract class MixinThreadedAnvilChunkStorage { - - @Accessor("world") - public abstract ServerWorld getWorld(); - - @Inject(at = @At("RETURN"), method = "upgradeChunk") - public void upgradeChunk(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable>> ci) { - if (requiredStatus == ChunkStatus.FULL) { - ChunkFinalizeCallback.EVENT.invoker().onChunkFinalized(getWorld(), new Vector2i(holder.getPos().x, holder.getPos().z)); - } - } - -} diff --git a/implementations/fabric-1.16.2/src/main/resources/bluemap.mixins.json b/implementations/fabric-1.16.2/src/main/resources/bluemap.mixins.json index 973877f8..2bfdbf9e 100644 --- a/implementations/fabric-1.16.2/src/main/resources/bluemap.mixins.json +++ b/implementations/fabric-1.16.2/src/main/resources/bluemap.mixins.json @@ -6,9 +6,7 @@ "mixins": [], "client": [], "server": [ - "MixinThreadedAnvilChunkStorage", - "MixinPlayerManager", - "MixinServerWorld" + "MixinPlayerManager" ], "injectors": { "defaultRequire": 1 diff --git a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf index ef3bb021..74d53aa7 100644 --- a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf +++ b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -2,4 +2,5 @@ liveUpdates: true skinDownload: true hiddenGameModes: [] hideInvisible: true -hideSneaking: false \ No newline at end of file +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin.conf index 67f297e2..2d3ec7e3 100644 --- a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin.conf +++ b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -24,4 +24,9 @@ hideInvisible: true # If this is true, players that are sneaking will be hidden on the map. # Default is false -hideSneaking: false \ No newline at end of file +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf index 2f276c92..3b775ae3 100644 --- a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf +++ b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -1,3 +1,4 @@ webroot: "bluemap/web" useCookies: true +enableFreeFlight: true maps: [] diff --git a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf index 1efd4978..4a602264 100644 --- a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "bluemap/web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/fabric-1.17/build.gradle b/implementations/fabric-1.17/build.gradle new file mode 100644 index 00000000..f89799d4 --- /dev/null +++ b/implementations/fabric-1.17/build.gradle @@ -0,0 +1,64 @@ +import net.fabricmc.loom.task.RemapJarTask + +plugins { + id 'fabric-loom' version '0.8-SNAPSHOT' +} + +configurations { + implementation.extendsFrom shadowInclude +} + +dependencies { + minecraft "com.mojang:minecraft:1.17" + mappings "net.fabricmc:yarn:1.17+build.1:v2" + modImplementation "net.fabricmc:fabric-loader:0.11.3" + modImplementation "net.fabricmc.fabric-api:fabric-api:0.34.9+1.17" + + shadowInclude (project(':BlueMapCommon')) { + //exclude dependencies provided by fabric + exclude group: 'com.google.guava', module: 'guava' + exclude group: 'com.google.code.gson', module: 'gson' + exclude group: 'org.apache.commons', module: 'commons-lang3' + exclude group: 'commons-io', module: 'commons-io' + exclude group: 'com.mojang', module: 'brigadier' + } +} + +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +shadowJar { + configurations = [project.configurations.shadowInclude] + + //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it + relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' + relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' + relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' + relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' + relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' + relocate 'javax.inject', 'de.bluecolored.shadow.javax.inject' + relocate 'com.google.inject', 'de.bluecolored.shadow.google.inject' + relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' + relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' +} + +task ramappedShadowJar(type: RemapJarTask) { + destinationDirectory = file '../../build/release' + dependsOn tasks.shadowJar + input.set(tasks.shadowJar.archiveFile.get()) + addNestedDependencies.set(true) + archiveFileName.set("BlueMap-${archiveVersion.get()}-fabric-1.17.jar") +} +build.dependsOn ramappedShadowJar + +task sourcesJar(type: Jar, dependsOn: classes) { + archiveClassifier.set("sources") + from sourceSets.main.allSource +} diff --git a/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricCommandSource.java b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricCommandSource.java new file mode 100644 index 00000000..ed9b1542 --- /dev/null +++ b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricCommandSource.java @@ -0,0 +1,84 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.fabric; + +import java.io.IOException; +import java.util.Optional; + +import com.flowpowered.math.vector.Vector3d; + +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; +import de.bluecolored.bluemap.common.plugin.text.Text; +import de.bluecolored.bluemap.core.world.World; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Vec3d; + +public class FabricCommandSource implements CommandSource { + + private FabricMod mod; + private Plugin plugin; + private ServerCommandSource delegate; + + public FabricCommandSource(FabricMod mod, Plugin plugin, ServerCommandSource delegate) { + this.mod = mod; + this.plugin = plugin; + this.delegate = delegate; + } + + @Override + public void sendMessage(Text text) { + delegate.sendFeedback(net.minecraft.text.Text.Serializer.fromJson(text.toJSONString()), false); + } + + @Override + public boolean hasPermission(String permission) { + return delegate.hasPermissionLevel(1); + } + + @Override + public Optional getPosition() { + Vec3d pos = delegate.getPosition(); + if (pos != null) { + return Optional.of(new Vector3d(pos.x, pos.y, pos.z)); + } + + return Optional.empty(); + } + + @Override + public Optional getWorld() { + try { + ServerWorld world = delegate.getWorld(); + if (world != null) { + return Optional.ofNullable(plugin.getWorld(mod.getUUIDForWorld(world))); + } + } catch (IOException ignore) {} + + return Optional.empty(); + } + +} diff --git a/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java new file mode 100644 index 00000000..ade2b042 --- /dev/null +++ b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricEventForwarder.java @@ -0,0 +1,72 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.fabric; + +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; +import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + +public class FabricEventForwarder { + + private FabricMod mod; + private Collection eventListeners; + + public FabricEventForwarder(FabricMod mod) { + this.mod = mod; + this.eventListeners = new ArrayList<>(1); + + PlayerJoinCallback.EVENT.register(this::onPlayerJoin); + PlayerLeaveCallback.EVENT.register(this::onPlayerLeave); + } + + public synchronized void addEventListener(ServerEventListener listener) { + this.eventListeners.add(listener); + } + + public synchronized void removeAllListeners() { + this.eventListeners.clear(); + } + + public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) { + if (this.mod.getServer() != server) return; + + UUID uuid = player.getUuid(); + for (ServerEventListener listener : eventListeners) listener.onPlayerJoin(uuid); + } + + public synchronized void onPlayerLeave(MinecraftServer server, ServerPlayerEntity player) { + if (this.mod.getServer() != server) return; + + UUID uuid = player.getUuid(); + for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid); + } + +} diff --git a/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java new file mode 100644 index 00000000..af2d7452 --- /dev/null +++ b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java @@ -0,0 +1,256 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.fabric; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.commands.Commands; +import de.bluecolored.bluemap.common.plugin.serverinterface.Player; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; +import de.bluecolored.bluemap.core.BlueMap; +import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; +import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; +import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.WorldSavePath; +import net.minecraft.world.dimension.DimensionType; +import org.apache.logging.log4j.LogManager; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; + +public class FabricMod implements ModInitializer, ServerInterface { + + private Plugin pluginInstance = null; + private MinecraftServer serverInstance = null; + + private Map worldUUIDs; + private FabricEventForwarder eventForwarder; + + private LoadingCache worldUuidCache; + + private int playerUpdateIndex = 0; + private Map onlinePlayerMap; + private List onlinePlayerList; + + public FabricMod() { + Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME)); + + this.onlinePlayerMap = new ConcurrentHashMap<>(); + this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); + + pluginInstance = new Plugin(new MinecraftVersion(1, 17), "fabric-1.17", this); + + this.worldUUIDs = new ConcurrentHashMap<>(); + this.eventForwarder = new FabricEventForwarder(this); + this.worldUuidCache = Caffeine.newBuilder() + .executor(BlueMap.THREAD_POOL) + .weakKeys() + .maximumSize(1000) + .build(this::loadUUIDForWorld); + } + + @Override + public void onInitialize() { + + //register commands + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { + new Commands<>(pluginInstance, dispatcher, fabricSource -> new FabricCommandSource(this, pluginInstance, fabricSource)); + }); + + ServerLifecycleEvents.SERVER_STARTED.register((MinecraftServer server) -> { + this.serverInstance = server; + + new Thread(()->{ + Logger.global.logInfo("Loading BlueMap..."); + + try { + pluginInstance.load(); + if (pluginInstance.isLoaded()) Logger.global.logInfo("BlueMap loaded!"); + } catch (IOException | ParseResourceException e) { + Logger.global.logError("Failed to load bluemap!", e); + pluginInstance.unload(); + } + }).start(); + }); + + ServerLifecycleEvents.SERVER_STOPPING.register((MinecraftServer server) -> { + pluginInstance.unload(); + Logger.global.logInfo("BlueMap unloaded!"); + }); + + PlayerJoinCallback.EVENT.register(this::onPlayerJoin); + PlayerLeaveCallback.EVENT.register(this::onPlayerLeave); + + ServerTickEvents.END_SERVER_TICK.register((MinecraftServer server) -> { + if (server == this.serverInstance) this.updateSomePlayers(); + }); + } + + @Override + public void registerListener(ServerEventListener listener) { + eventForwarder.addEventListener(listener); + } + + @Override + public void unregisterAllListeners() { + eventForwarder.removeAllListeners(); + } + + @Override + public UUID getUUIDForWorld(File worldFolder) throws IOException { + worldFolder = worldFolder.getCanonicalFile(); + + UUID uuid = worldUUIDs.get(worldFolder); + if (uuid == null) { + uuid = UUID.randomUUID(); + worldUUIDs.put(worldFolder, uuid); + } + + return uuid; + } + + public UUID getUUIDForWorld(ServerWorld world) throws IOException { + try { + return worldUuidCache.get(world); + } catch (RuntimeException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException) throw (IOException) cause; + else throw new IOException(cause); + } + } + + private UUID loadUUIDForWorld(ServerWorld world) throws IOException { + MinecraftServer server = world.getServer(); + File worldFolder = world.getServer().getRunDirectory().toPath().resolve(server.getSavePath(WorldSavePath.ROOT)).toFile(); + File dimensionFolder = DimensionType.getSaveDirectory(world.getRegistryKey(), worldFolder); + File dimensionDir = dimensionFolder.getCanonicalFile(); + return getUUIDForWorld(dimensionDir); + } + + @Override + public boolean persistWorldChanges(UUID worldUUID) throws IOException, IllegalArgumentException { + final CompletableFuture taskResult = new CompletableFuture<>(); + + serverInstance.execute(() -> { + try { + for (ServerWorld world : serverInstance.getWorlds()) { + if (getUUIDForWorld(world).equals(worldUUID)) { + world.save(null, true, false); + } + } + + taskResult.complete(true); + } catch (Exception e) { + taskResult.completeExceptionally(e); + } + }); + + try { + return taskResult.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException(e); + } catch (ExecutionException e) { + Throwable t = e.getCause(); + if (t instanceof IOException) throw (IOException) t; + if (t instanceof IllegalArgumentException) throw (IllegalArgumentException) t; + throw new IOException(t); + } + } + + @Override + public File getConfigFolder() { + return new File("config/bluemap"); + } + + public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) { + if (this.serverInstance != server) return; + + FabricPlayer player = new FabricPlayer(this, playerInstance.getUuid()); + onlinePlayerMap.put(player.getUuid(), player); + onlinePlayerList.add(player); + } + + public void onPlayerLeave(MinecraftServer server, ServerPlayerEntity player) { + if (this.serverInstance != server) return; + + UUID playerUUID = player.getUuid(); + onlinePlayerMap.remove(playerUUID); + synchronized (onlinePlayerList) { + onlinePlayerList.removeIf(p -> p.getUuid().equals(playerUUID)); + } + } + + public MinecraftServer getServer() { + return this.serverInstance; + } + + @Override + public Collection getOnlinePlayers() { + return onlinePlayerMap.values(); + } + + @Override + public Optional getPlayer(UUID uuid) { + return Optional.ofNullable(onlinePlayerMap.get(uuid)); + } + + /** + * Only update some of the online players each tick to minimize performance impact on the server-thread. + * Only call this method on the server-thread. + */ + private void updateSomePlayers() { + int onlinePlayerCount = onlinePlayerList.size(); + if (onlinePlayerCount == 0) return; + + int playersToBeUpdated = onlinePlayerCount / 20; //with 20 tps, each player is updated once a second + if (playersToBeUpdated == 0) playersToBeUpdated = 1; + + for (int i = 0; i < playersToBeUpdated; i++) { + playerUpdateIndex++; + if (playerUpdateIndex >= 20 && playerUpdateIndex >= onlinePlayerCount) playerUpdateIndex = 0; + + if (playerUpdateIndex < onlinePlayerCount) { + onlinePlayerList.get(playerUpdateIndex).update(); + } + } + } + +} diff --git a/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricPlayer.java b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricPlayer.java new file mode 100644 index 00000000..3053d8ff --- /dev/null +++ b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/FabricPlayer.java @@ -0,0 +1,150 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.fabric; + +import java.io.IOException; +import java.util.EnumMap; +import java.util.Map; +import java.util.UUID; + +import com.flowpowered.math.vector.Vector3d; + +import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode; +import de.bluecolored.bluemap.common.plugin.serverinterface.Player; +import de.bluecolored.bluemap.common.plugin.text.Text; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.GameMode; + +public class FabricPlayer implements Player { + + private static final UUID UNKNOWN_WORLD_UUID = UUID.randomUUID(); + + private static final Map GAMEMODE_MAP = new EnumMap<>(GameMode.class); + static { + GAMEMODE_MAP.put(GameMode.ADVENTURE, Gamemode.ADVENTURE); + GAMEMODE_MAP.put(GameMode.SURVIVAL, Gamemode.SURVIVAL); + GAMEMODE_MAP.put(GameMode.CREATIVE, Gamemode.CREATIVE); + GAMEMODE_MAP.put(GameMode.SPECTATOR, Gamemode.SPECTATOR); + } + + private UUID uuid; + private Text name; + private UUID world; + private Vector3d position; + private boolean online; + private boolean sneaking; + private boolean invisible; + private Gamemode gamemode; + + private FabricMod mod; + + public FabricPlayer(FabricMod mod, UUID playerUuid) { + this.uuid = playerUuid; + this.mod = mod; + + update(); + } + + @Override + public UUID getUuid() { + return this.uuid; + } + + @Override + public Text getName() { + return this.name; + } + + @Override + public UUID getWorld() { + return this.world; + } + + @Override + public Vector3d getPosition() { + return this.position; + } + + @Override + public boolean isOnline() { + return this.online; + } + + @Override + public boolean isSneaking() { + return this.sneaking; + } + + @Override + public boolean isInvisible() { + return this.invisible; + } + + @Override + public Gamemode getGamemode() { + return this.gamemode; + } + + /** + * Only call on server thread! + */ + public void update() { + MinecraftServer server = mod.getServer(); + if (server == null) { + this.online = false; + return; + } + + ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid); + if (player == null) { + this.online = false; + return; + } + + this.gamemode = GAMEMODE_MAP.get(player.interactionManager.getGameMode()); + if (this.gamemode == null) this.gamemode = Gamemode.SURVIVAL; + + StatusEffectInstance invis = player.getStatusEffect(StatusEffects.INVISIBILITY); + this.invisible = invis != null && invis.getDuration() > 0; + + this.name = Text.of(player.getName().getString()); + this.online = true; + + Vec3d pos = player.getPos(); + this.position = new Vector3d(pos.getX(), pos.getY(), pos.getZ()); + this.sneaking = player.isSneaking(); + + try { + this.world = mod.getUUIDForWorld(player.getServerWorld()); + } catch (IOException e) { + this.world = UNKNOWN_WORLD_UUID; + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/StaticRenderSettings.java b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/Log4jLogger.java similarity index 61% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/StaticRenderSettings.java rename to implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/Log4jLogger.java index 62e7d2f0..2d298f63 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/StaticRenderSettings.java +++ b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/Log4jLogger.java @@ -22,46 +22,48 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render; +package de.bluecolored.bluemap.fabric; -import com.flowpowered.math.vector.Vector3i; +import org.apache.logging.log4j.Logger; -public class StaticRenderSettings implements RenderSettings { +import de.bluecolored.bluemap.core.logger.AbstractLogger; + +public class Log4jLogger extends AbstractLogger { + + private Logger out; - private boolean excludeFacesWithoutSunlight; - private Vector3i min, max; - private boolean renderEdges; - - public StaticRenderSettings( - boolean excludeFacesWithoutSunlight, - Vector3i min, - Vector3i max, - boolean renderEdges - ) { - this.excludeFacesWithoutSunlight = excludeFacesWithoutSunlight; - this.min = min; - this.max = max; - this.renderEdges = renderEdges; + public Log4jLogger(Logger out) { + this.out = out; } @Override - public boolean isExcludeFacesWithoutSunlight() { - return excludeFacesWithoutSunlight; + public void logError(String message, Throwable throwable) { + out.error(message, throwable); + } + + @Override + public void logWarning(String message) { + out.warn(message); + } + + @Override + public void logInfo(String message) { + out.info(message); + } + + @Override + public void logDebug(String message) { + if (out.isDebugEnabled()) out.debug(message); } @Override - public Vector3i getMin() { - return min; + public void noFloodDebug(String message) { + if (out.isDebugEnabled()) super.noFloodDebug(message); } @Override - public Vector3i getMax() { - return max; + public void noFloodDebug(String key, String message) { + if (out.isDebugEnabled()) super.noFloodDebug(key, message); } - @Override - public boolean isRenderEdges() { - return renderEdges; - } - -} \ No newline at end of file +} diff --git a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/events/PlayerJoinCallback.java similarity index 76% rename from implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java rename to implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/events/PlayerJoinCallback.java index 25b2ea9f..d564012c 100644 --- a/implementations/fabric-1.16.1/src/main/java/de/bluecolored/bluemap/fabric/events/WorldSaveCallback.java +++ b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/events/PlayerJoinCallback.java @@ -26,16 +26,17 @@ package de.bluecolored.bluemap.fabric.events; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; -public interface WorldSaveCallback { - Event EVENT = EventFactory.createArrayBacked(WorldSaveCallback.class, - (listeners) -> (world) -> { - for (WorldSaveCallback event : listeners) { - event.onWorldSaved(world); +public interface PlayerJoinCallback { + Event EVENT = EventFactory.createArrayBacked(PlayerJoinCallback.class, + (listeners) -> (server, player) -> { + for (PlayerJoinCallback event : listeners) { + event.onPlayerJoin(server, player); } } ); - void onWorldSaved(ServerWorld world); + void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player); } diff --git a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/events/PlayerLeaveCallback.java similarity index 76% rename from implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java rename to implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/events/PlayerLeaveCallback.java index 49de240d..f1ce2922 100644 --- a/implementations/fabric-1.15.2/src/main/java/de/bluecolored/bluemap/fabric/events/ChunkFinalizeCallback.java +++ b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/events/PlayerLeaveCallback.java @@ -24,20 +24,19 @@ */ package de.bluecolored.bluemap.fabric.events; -import com.flowpowered.math.vector.Vector2i; - import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; -public interface ChunkFinalizeCallback { - Event EVENT = EventFactory.createArrayBacked(ChunkFinalizeCallback.class, - (listeners) -> (world, chunkPos) -> { - for (ChunkFinalizeCallback event : listeners) { - event.onChunkFinalized(world, chunkPos); +public interface PlayerLeaveCallback { + Event EVENT = EventFactory.createArrayBacked(PlayerLeaveCallback.class, + (listeners) -> (server, player) -> { + for (PlayerLeaveCallback event : listeners) { + event.onPlayerLeave(server, player); } } ); - void onChunkFinalized(ServerWorld world, Vector2i chunkPos); + void onPlayerLeave(MinecraftServer server, ServerPlayerEntity player); } diff --git a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinPlayerManager.java similarity index 62% rename from implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java rename to implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinPlayerManager.java index d2647bfa..b7109357 100644 --- a/implementations/fabric-1.16.2/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinServerWorld.java +++ b/implementations/fabric-1.17/src/main/java/de/bluecolored/bluemap/fabric/mixin/MixinPlayerManager.java @@ -25,20 +25,32 @@ package de.bluecolored.bluemap.fabric.mixin; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import de.bluecolored.bluemap.fabric.events.WorldSaveCallback; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.ProgressListener; +import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; +import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; +import net.minecraft.network.ClientConnection; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ServerPlayerEntity; -@Mixin(ServerWorld.class) -public abstract class MixinServerWorld { +@Mixin(PlayerManager.class) +public abstract class MixinPlayerManager { - @Inject(at = @At("RETURN"), method = "save") - public void save(ProgressListener progressListener, boolean flush, boolean bl, CallbackInfo ci) { - WorldSaveCallback.EVENT.invoker().onWorldSaved((ServerWorld) (Object) this); + @Shadow + public abstract MinecraftServer getServer(); + + @Inject(at = @At("RETURN"), method = "onPlayerConnect") + public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) { + PlayerJoinCallback.EVENT.invoker().onPlayerJoin(this.getServer(), player); + } + + @Inject(at = @At("HEAD"), method = "remove") + public void remove(ServerPlayerEntity player, CallbackInfo ci) { + PlayerLeaveCallback.EVENT.invoker().onPlayerLeave(this.getServer(), player); } } diff --git a/implementations/fabric-1.17/src/main/resources/assets/bluemap/icon.png b/implementations/fabric-1.17/src/main/resources/assets/bluemap/icon.png new file mode 100644 index 00000000..65d56d7a Binary files /dev/null and b/implementations/fabric-1.17/src/main/resources/assets/bluemap/icon.png differ diff --git a/implementations/fabric-1.17/src/main/resources/bluemap.mixins.json b/implementations/fabric-1.17/src/main/resources/bluemap.mixins.json new file mode 100644 index 00000000..2bfdbf9e --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/bluemap.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "de.bluecolored.bluemap.fabric.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [], + "client": [], + "server": [ + "MixinPlayerManager" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/core-defaults.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/core-defaults.conf new file mode 100644 index 00000000..ace808ab --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/core-defaults.conf @@ -0,0 +1,4 @@ +accept-download: false +renderThreadCount: -2 +metrics: true +data: "bluemap" \ No newline at end of file diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/core.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/core.conf new file mode 100644 index 00000000..0be0b0a2 --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/core.conf @@ -0,0 +1,30 @@ +## ## +## BlueMap ## +## Core-Config ## +## ## + +# By changing the setting (accept-download) below to TRUE you are indicating that you have accepted mojang's EULA (https://account.mojang.com/documents/minecraft_eula), +# you confirm that you own a license to Minecraft (Java Edition) +# and you agree that BlueMap will download and use a minecraft-client file (depending on the minecraft-version) from mojangs servers (https://launcher.mojang.com/) for you. +# This file contains resources that belong to mojang and you must not redistribute it or do anything else that is not compliant with mojang's EULA. +# BlueMap uses resources in this file to generate the 3D-Models used for the map and texture them. (BlueMap will not work without those resources.) +# %datetime-iso% +accept-download: false + +# This changes the amount of threads that BlueMap will use to render the maps. +# A higher value can improve render-speed but could impact performance on the host machine. +# This should be always below or equal to the number of available processor-cores. +# Zero or a negative value means the amount of of available processor-cores subtracted by the value. +# (So a value of -2 with 6 cores results in 4 render-processes) +# Default is -2 +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":"bukkit","version":"%version%"} +# Default is true +metrics: true + +# The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later. +# Default is "bluemap" +data: "bluemap" \ No newline at end of file diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf new file mode 100644 index 00000000..74d53aa7 --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -0,0 +1,6 @@ +liveUpdates: true +skinDownload: true +hiddenGameModes: [] +hideInvisible: true +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/plugin.conf new file mode 100644 index 00000000..2d3ec7e3 --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -0,0 +1,32 @@ +## ## +## BlueMap ## +## Plugin-Config ## +## ## + +# If the server should send live-updates and player-positions. +# This only works if the integrated webserver is enabled. +# Default is true +liveUpdates: true + +# Download the skin from mojang-serves when a player joins your server, so it can be used for the player-markers. +# Default is true +skinDownload: true + +# A list of gamemodes that will prevent a player from appearing on the map. +# Possible values are: survival, creative, spectator, adventure +hiddenGameModes: [ + "spectator" +] + +# If this is true, players that have an invisibility (potion-)effect will be hidden on the map. +# Default is true +hideInvisible: true + +# If this is true, players that are sneaking will be hidden on the map. +# Default is false +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render-defaults.conf new file mode 100644 index 00000000..3b775ae3 --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -0,0 +1,4 @@ +webroot: "bluemap/web" +useCookies: true +enableFreeFlight: true +maps: [] diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render.conf new file mode 100644 index 00000000..4a602264 --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render.conf @@ -0,0 +1,130 @@ +## ## +## BlueMap ## +## Render-Config ## +## ## + +# The folder (webroot) where the map-data and web-application files will be saved. +# Default is "bluemap/web" +webroot: "bluemap/web" + +# If the web-application should use cookies to save the configurations of a user. +# Default is true +useCookies: true + +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + +# This is an array with multiple configured maps. +# You can define multiple maps, for different worlds with different render-settings here +maps: [ + + { + # The id of this map + # Should only contain word-charactes: [a-zA-Z0-9_] + # Changing this value breaks your existing renders. + id: "world" + + # 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: "World" + + # 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. + # You can change this at any time. + # This defaults to the world-spawn if you don't set it. + #startPos: [500, -820] + + # The color of thy sky as a hex-color + # You can change this at any time. + # Default is "#7dabff" + skyColor: "#7dabff" + + # Defines the ambient light-strength that every block is recieving, regardless of the sunlight/blocklight. + # 0 is no ambient light, 1 is fully lighted. + # You can change this at any time. + # Default is 0 + ambientLight: 0 + + # 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. + # 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. + # Changing this value requires a re-render of the map. + # Default is false + renderCaves: false + + # With the below values you can limit the map-render. + # This can be used to ignore the nethers ceiling or render only a certain part of a world. + # Changing this values might require a re-render of the map, already rendered tiles outside the limits will not be deleted. + # Default is no min or max value (= infinite bounds) + #minX: -4000 + #maxX: 4000 + #minZ: -4000 + #maxZ: 4000 + #minY: 50 + #maxY: 126 + + # Using this, BlueMap pretends that every Block out of the defined render-bounds is AIR, + # this means you can see the blocks where the world is cut (instead of having a see-through/xray view). + # This has only an effect if you set some render-bounds above. + # Changing this value requires a re-render of the map. + # Default is true + renderEdges: true + + # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. + # Files will be only 5% as big with compression! + # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. + # This is much better than disabling the compression. + # Changing this value requires a re-render of the map. + # Default is true + useCompression: true + + # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. + # If this is set to true BlueMap will render Chunks even if there is no light-data! + # This can be usefull for example if some mod prevents light-data from being saved correctly. + # However, this also has a few drawbacks: + # - For those chunks, every block will always be fully lit + # - Night-mode might not work correctly + # - Caves will always be rendered (ignoring the 'renderCaves' setting) + # Default is false + ignoreMissingLightData: false + } + + # Here another example for the End-Map + # Things we don't want to change from default we can just omit + { + id: "end" + name: "End" + world: "world/DIM1" + + # We dont want a blue sky in the end + skyColor: "#080010" + + # In the end is no sky-light, so we need to enable this or we won't see anything. + renderCaves: true + + # Same here, we don't want a dark map. But not completely lighted, so we see the effect of e.g torches. + ambientLight: 0.6 + } + + # Here another example for the Nether-Map + { + id: "nether" + name: "Nether" + world: "world/DIM-1" + + skyColor: "#290000" + + renderCaves: true + ambientLight: 0.6 + + # We slice the whole world at y:90 so every block above 90 will be air. + # This way we don't render the nethers ceiling. + maxY: 90 + renderEdges: true + } + +] diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/webserver-defaults.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/webserver-defaults.conf new file mode 100644 index 00000000..55e8c255 --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/webserver-defaults.conf @@ -0,0 +1,4 @@ +enabled: true +webroot: "bluemap/web" +port: 8100 +maxConnectionCount: 100 \ No newline at end of file diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/webserver.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/webserver.conf new file mode 100644 index 00000000..6af072df --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/webserver.conf @@ -0,0 +1,29 @@ +## ## +## BlueMap ## +## Webserver-Config ## +## ## + +# With this setting you can disable the integrated web-server. +# This is usefull if you want to only render the map-data for later use, or if you setup your own webserver. +# Default is enabled +enabled: true + +# The webroot that the server will host to the web. +# Usually this should be set to the same directory like in the render.conf! +# Default is "bluemap/web" +webroot: "bluemap/web" + +# The IP-Adress that the webserver binds to. +# Use "0.0.0.0" to bind to all available local adresses. +# If you only want to access it locally use "localhost". +# Default is "0.0.0.0" +#ip: "localhost" +#ip: "123.45.6.78" + +# The port that the webserver listenes to. +# Default is 8100 +port: 8100 + +# Max number of simultaneous connections that the webserver allows +# Default is 100 +maxConnectionCount: 100 \ No newline at end of file diff --git a/implementations/fabric-1.17/src/main/resources/fabric.mod.json b/implementations/fabric-1.17/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..6a8da908 --- /dev/null +++ b/implementations/fabric-1.17/src/main/resources/fabric.mod.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "id": "bluemap", + "version": "${version}", + + "name": "BlueMap", + "description": "A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL)", + "authors": [ + "Blue (TBlueF, Lukas Rieger)" + ], + "contact": { + "homepage": "https://github.com/BlueMap-Minecraft", + "sources": "https://github.com/BlueMap-Minecraft/BlueMap" + }, + + "license": "MIT", + "icon": "assets/bluemap/icon.png", + + "environment": "*", + "entrypoints": { + "main": [ + "de.bluecolored.bluemap.fabric.FabricMod" + ] + }, + "mixins": [ + "bluemap.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.11.3", + "fabric": "*", + "fabric-api-base": "*", + "minecraft": ">=1.17.0" + }, + "suggests": {} +} diff --git a/implementations/forge-1.14.4/build.gradle b/implementations/forge-1.14.4/build.gradle index 02d541b3..3f073161 100644 --- a/implementations/forge-1.14.4/build.gradle +++ b/implementations/forge-1.14.4/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.0.9', changing: true } } @@ -16,7 +16,7 @@ minecraft { } configurations { - compile.extendsFrom include + implementation.extendsFrom include } dependencies { @@ -33,15 +33,15 @@ dependencies { } build.dependsOn shadowJar { - destinationDir = file '../../build/release' - archiveFileName = "BlueMap-${version}-forge-1.14.4.jar" + destinationDirectory = file '../../build/release' + archiveFileName.set("BlueMap-${archiveVersion.get()}-forge-1.14.4.jar") configurations = [project.configurations.include] //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' @@ -49,11 +49,13 @@ build.dependsOn shadowJar { relocate 'com.google.inject', 'de.bluecolored.shadow.google.inject' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } processResources { from(sourceSets.main.resources.srcDirs) { include 'mcmod.info','META-INF/mods.toml' + duplicatesStrategy = DuplicatesStrategy.WARN expand ( version: project.version diff --git a/implementations/forge-1.14.4/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java b/implementations/forge-1.14.4/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java index 1ce6b9fc..c263067d 100644 --- a/implementations/forge-1.14.4/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java +++ b/implementations/forge-1.14.4/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java @@ -24,48 +24,24 @@ */ package de.bluecolored.bluemap.forge; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedDeque; - -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.world.World; -import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; -import net.minecraftforge.event.world.BlockEvent; -import net.minecraftforge.event.world.ChunkDataEvent; -import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + public class ForgeEventForwarder { + + private final Collection eventListeners; - private ForgeMod mod; - private Collection eventListeners; - - private Deque loadChunkEvents; - private Thread loadChunkEventProcessor; - - public ForgeEventForwarder(ForgeMod mod) { - this.mod = mod; + public ForgeEventForwarder() { this.eventListeners = new ArrayList<>(1); - - loadChunkEvents = new ConcurrentLinkedDeque<>(); MinecraftForge.EVENT_BUS.register(this); - - //see processLoadChunkEvents JavaDoc comment - loadChunkEventProcessor = new Thread(this::processLoadChunkEvents); - loadChunkEventProcessor.setDaemon(true); - loadChunkEventProcessor.start(); } public synchronized void addEventListener(ServerEventListener listener) { @@ -75,62 +51,6 @@ public class ForgeEventForwarder { public synchronized void removeAllListeners() { this.eventListeners.clear(); } - - @SubscribeEvent - public void onBlockBreak(BlockEvent.BreakEvent evt) { - onBlockChange(evt); - } - - @SubscribeEvent - public void onBlockPlace(BlockEvent.EntityPlaceEvent evt) { - onBlockChange(evt); - } - - private synchronized void onBlockChange(BlockEvent evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - Vector3i position = new Vector3i( - evt.getPos().getX(), - evt.getPos().getY(), - evt.getPos().getZ() - ); - - for (ServerEventListener listener : eventListeners) listener.onBlockChange(world, position); - - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - @SubscribeEvent - public synchronized void onChunkSave(ChunkDataEvent.Save evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - Vector2i chunkPos = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z); - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - for (ServerEventListener listener : eventListeners) listener.onChunkSaveToDisk(world, chunkPos); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - /* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk - @SubscribeEvent - public synchronized void onWorldSave(WorldEvent.Save evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - */ @SubscribeEvent public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) { @@ -143,64 +63,5 @@ public class ForgeEventForwarder { UUID uuid = evt.getPlayer().getUniqueID(); for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid); } - - @SubscribeEvent - public void onChunkLoad(ChunkEvent.Load evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - Vector2i chunk = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z); - - synchronized (loadChunkEvents) { - loadChunkEvents.add(new WorldChunk(world, chunk)); - loadChunkEvents.notify(); - } - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - /** - * This is a workaround for forge not providing a way to detect if chunks are newly generated: - * Each time a chunk-load-event occurs, it is (asynchronously) tested if the chunk is already generated on the world files. - * If it is a new chunk it will likely not be saved to the disk right away. - */ - private void processLoadChunkEvents() { - while (!Thread.interrupted()) { - WorldChunk worldChunk; - if (mod.getPlugin().isLoaded() && (worldChunk = loadChunkEvents.poll()) != null) { - try { - World world = mod.getPlugin().getWorld(worldChunk.world); - if (world == null || world.isChunkGenerated(worldChunk.chunk)) continue; - - for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(worldChunk.world, worldChunk.chunk); - - } catch (RuntimeException e) { - Logger.global.noFloodWarning("processLoadChunkEventsError", "Failed to test if a chunk is newly generated:" + e); - } - } else { - synchronized (loadChunkEvents) { - try { - loadChunkEvents.wait(10000); - } catch (InterruptedException e) { - break; - } - } - } - } - - Thread.currentThread().interrupt(); - } - - private class WorldChunk { - final UUID world; - final Vector2i chunk; - - public WorldChunk(UUID world, Vector2i chunk) { - this.world = world; - this.chunk = chunk; - } - } } diff --git a/implementations/forge-1.14.4/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java b/implementations/forge-1.14.4/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java index 58b6a6b2..55354b97 100644 --- a/implementations/forge-1.14.4/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java +++ b/implementations/forge-1.14.4/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java @@ -24,25 +24,8 @@ */ package de.bluecolored.bluemap.forge; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import net.minecraftforge.fml.event.server.FMLServerStartedEvent; -import org.apache.logging.log4j.LogManager; - import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; - import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.plugin.serverinterface.Player; @@ -62,8 +45,17 @@ import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.server.FMLServerStartedEvent; import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; +import org.apache.logging.log4j.LogManager; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; @Mod(Plugin.PLUGIN_ID) public class ForgeMod implements ServerInterface { @@ -86,10 +78,10 @@ public class ForgeMod implements ServerInterface { this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); - this.pluginInstance = new Plugin(MinecraftVersion.MC_1_14, "forge-1.14.4", this); + this.pluginInstance = new Plugin(new MinecraftVersion(1, 14, 4), "forge-1.14.4", this); this.worldUUIDs = new ConcurrentHashMap<>(); - this.eventForwarder = new ForgeEventForwarder(this); + this.eventForwarder = new ForgeEventForwarder(); this.worldUuidCache = Caffeine.newBuilder() .executor(BlueMap.THREAD_POOL) .weakKeys() diff --git a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf index ef3bb021..74d53aa7 100644 --- a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf +++ b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -2,4 +2,5 @@ liveUpdates: true skinDownload: true hiddenGameModes: [] hideInvisible: true -hideSneaking: false \ No newline at end of file +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/plugin.conf index 67f297e2..2d3ec7e3 100644 --- a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/plugin.conf +++ b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -24,4 +24,9 @@ hideInvisible: true # If this is true, players that are sneaking will be hidden on the map. # Default is false -hideSneaking: false \ No newline at end of file +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render-defaults.conf index 2f276c92..3b775ae3 100644 --- a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render-defaults.conf +++ b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -1,3 +1,4 @@ webroot: "bluemap/web" useCookies: true +enableFreeFlight: true maps: [] diff --git a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render.conf index 1efd4978..4a602264 100644 --- a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "bluemap/web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/forge-1.15.2/build.gradle b/implementations/forge-1.15.2/build.gradle index 81e5cb3e..e0aaa906 100644 --- a/implementations/forge-1.15.2/build.gradle +++ b/implementations/forge-1.15.2/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.0.9', changing: true } } @@ -16,7 +16,7 @@ minecraft { } configurations { - compile.extendsFrom include + implementation.extendsFrom include } dependencies { @@ -33,15 +33,15 @@ dependencies { } build.dependsOn shadowJar { - destinationDir = file '../../build/release' - archiveFileName = "BlueMap-${version}-forge-1.15.2.jar" + destinationDirectory = file '../../build/release' + archiveFileName.set("BlueMap-${archiveVersion.get()}-forge-1.15.2.jar") configurations = [project.configurations.include] //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' @@ -49,11 +49,13 @@ build.dependsOn shadowJar { relocate 'com.google.inject', 'de.bluecolored.shadow.google.inject' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } processResources { from(sourceSets.main.resources.srcDirs) { include 'mcmod.info','META-INF/mods.toml' + duplicatesStrategy = DuplicatesStrategy.WARN expand ( version: project.version diff --git a/implementations/forge-1.15.2/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java b/implementations/forge-1.15.2/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java index 1ce6b9fc..c263067d 100644 --- a/implementations/forge-1.15.2/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java +++ b/implementations/forge-1.15.2/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java @@ -24,48 +24,24 @@ */ package de.bluecolored.bluemap.forge; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedDeque; - -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.world.World; -import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; -import net.minecraftforge.event.world.BlockEvent; -import net.minecraftforge.event.world.ChunkDataEvent; -import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + public class ForgeEventForwarder { + + private final Collection eventListeners; - private ForgeMod mod; - private Collection eventListeners; - - private Deque loadChunkEvents; - private Thread loadChunkEventProcessor; - - public ForgeEventForwarder(ForgeMod mod) { - this.mod = mod; + public ForgeEventForwarder() { this.eventListeners = new ArrayList<>(1); - - loadChunkEvents = new ConcurrentLinkedDeque<>(); MinecraftForge.EVENT_BUS.register(this); - - //see processLoadChunkEvents JavaDoc comment - loadChunkEventProcessor = new Thread(this::processLoadChunkEvents); - loadChunkEventProcessor.setDaemon(true); - loadChunkEventProcessor.start(); } public synchronized void addEventListener(ServerEventListener listener) { @@ -75,62 +51,6 @@ public class ForgeEventForwarder { public synchronized void removeAllListeners() { this.eventListeners.clear(); } - - @SubscribeEvent - public void onBlockBreak(BlockEvent.BreakEvent evt) { - onBlockChange(evt); - } - - @SubscribeEvent - public void onBlockPlace(BlockEvent.EntityPlaceEvent evt) { - onBlockChange(evt); - } - - private synchronized void onBlockChange(BlockEvent evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - Vector3i position = new Vector3i( - evt.getPos().getX(), - evt.getPos().getY(), - evt.getPos().getZ() - ); - - for (ServerEventListener listener : eventListeners) listener.onBlockChange(world, position); - - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - @SubscribeEvent - public synchronized void onChunkSave(ChunkDataEvent.Save evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - Vector2i chunkPos = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z); - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - for (ServerEventListener listener : eventListeners) listener.onChunkSaveToDisk(world, chunkPos); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - /* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk - @SubscribeEvent - public synchronized void onWorldSave(WorldEvent.Save evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - */ @SubscribeEvent public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) { @@ -143,64 +63,5 @@ public class ForgeEventForwarder { UUID uuid = evt.getPlayer().getUniqueID(); for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid); } - - @SubscribeEvent - public void onChunkLoad(ChunkEvent.Load evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - Vector2i chunk = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z); - - synchronized (loadChunkEvents) { - loadChunkEvents.add(new WorldChunk(world, chunk)); - loadChunkEvents.notify(); - } - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - /** - * This is a workaround for forge not providing a way to detect if chunks are newly generated: - * Each time a chunk-load-event occurs, it is (asynchronously) tested if the chunk is already generated on the world files. - * If it is a new chunk it will likely not be saved to the disk right away. - */ - private void processLoadChunkEvents() { - while (!Thread.interrupted()) { - WorldChunk worldChunk; - if (mod.getPlugin().isLoaded() && (worldChunk = loadChunkEvents.poll()) != null) { - try { - World world = mod.getPlugin().getWorld(worldChunk.world); - if (world == null || world.isChunkGenerated(worldChunk.chunk)) continue; - - for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(worldChunk.world, worldChunk.chunk); - - } catch (RuntimeException e) { - Logger.global.noFloodWarning("processLoadChunkEventsError", "Failed to test if a chunk is newly generated:" + e); - } - } else { - synchronized (loadChunkEvents) { - try { - loadChunkEvents.wait(10000); - } catch (InterruptedException e) { - break; - } - } - } - } - - Thread.currentThread().interrupt(); - } - - private class WorldChunk { - final UUID world; - final Vector2i chunk; - - public WorldChunk(UUID world, Vector2i chunk) { - this.world = world; - this.chunk = chunk; - } - } } diff --git a/implementations/forge-1.15.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java b/implementations/forge-1.15.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java index 056df1ca..dcde086d 100644 --- a/implementations/forge-1.15.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java +++ b/implementations/forge-1.15.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java @@ -24,25 +24,8 @@ */ package de.bluecolored.bluemap.forge; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import net.minecraftforge.fml.event.server.FMLServerStartedEvent; -import org.apache.logging.log4j.LogManager; - import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; - import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.plugin.serverinterface.Player; @@ -62,8 +45,17 @@ import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.server.FMLServerStartedEvent; import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; +import org.apache.logging.log4j.LogManager; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; @Mod(Plugin.PLUGIN_ID) public class ForgeMod implements ServerInterface { @@ -86,10 +78,10 @@ public class ForgeMod implements ServerInterface { this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); - this.pluginInstance = new Plugin(MinecraftVersion.MC_1_15, "forge-1.15.2", this); + this.pluginInstance = new Plugin(new MinecraftVersion(1, 15, 2), "forge-1.15.2", this); this.worldUUIDs = new ConcurrentHashMap<>(); - this.eventForwarder = new ForgeEventForwarder(this); + this.eventForwarder = new ForgeEventForwarder(); this.worldUuidCache = Caffeine.newBuilder() .executor(BlueMap.THREAD_POOL) .weakKeys() diff --git a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf index ef3bb021..74d53aa7 100644 --- a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf +++ b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -2,4 +2,5 @@ liveUpdates: true skinDownload: true hiddenGameModes: [] hideInvisible: true -hideSneaking: false \ No newline at end of file +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin.conf index 67f297e2..2d3ec7e3 100644 --- a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin.conf +++ b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -24,4 +24,9 @@ hideInvisible: true # If this is true, players that are sneaking will be hidden on the map. # Default is false -hideSneaking: false \ No newline at end of file +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf index 2f276c92..3b775ae3 100644 --- a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf +++ b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -1,3 +1,4 @@ webroot: "bluemap/web" useCookies: true +enableFreeFlight: true maps: [] diff --git a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf index 1efd4978..4a602264 100644 --- a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "bluemap/web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/forge-1.16.2/build.gradle b/implementations/forge-1.16.2/build.gradle index b57f08ac..055843a5 100644 --- a/implementations/forge-1.16.2/build.gradle +++ b/implementations/forge-1.16.2/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.0.9', changing: true } } @@ -16,7 +16,7 @@ minecraft { } configurations { - compile.extendsFrom include + implementation.extendsFrom include } dependencies { @@ -33,15 +33,15 @@ dependencies { } build.dependsOn shadowJar { - destinationDir = file '../../build/release' - archiveFileName = "BlueMap-${version}-forge-1.16.4.jar" + destinationDirectory = file '../../build/release' + archiveFileName.set("BlueMap-${archiveVersion.get()}-forge-1.16.4.jar") configurations = [project.configurations.include] //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' @@ -49,11 +49,13 @@ build.dependsOn shadowJar { relocate 'com.google.inject', 'de.bluecolored.shadow.google.inject' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } processResources { from(sourceSets.main.resources.srcDirs) { include 'mcmod.info','META-INF/mods.toml' + duplicatesStrategy = DuplicatesStrategy.WARN expand ( version: project.version diff --git a/implementations/forge-1.16.2/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java b/implementations/forge-1.16.2/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java index 2b6aeb23..c263067d 100644 --- a/implementations/forge-1.16.2/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java +++ b/implementations/forge-1.16.2/src/main/java/de/bluecolored/bluemap/forge/ForgeEventForwarder.java @@ -24,48 +24,24 @@ */ package de.bluecolored.bluemap.forge; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedDeque; - -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.world.World; -import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; -import net.minecraftforge.event.world.BlockEvent; -import net.minecraftforge.event.world.ChunkDataEvent; -import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + public class ForgeEventForwarder { + + private final Collection eventListeners; - private ForgeMod mod; - private Collection eventListeners; - - private Deque loadChunkEvents; - private Thread loadChunkEventProcessor; - - public ForgeEventForwarder(ForgeMod mod) { - this.mod = mod; + public ForgeEventForwarder() { this.eventListeners = new ArrayList<>(1); - - loadChunkEvents = new ConcurrentLinkedDeque<>(); MinecraftForge.EVENT_BUS.register(this); - - //see processLoadChunkEvents JavaDoc comment - loadChunkEventProcessor = new Thread(this::processLoadChunkEvents); - loadChunkEventProcessor.setDaemon(true); - loadChunkEventProcessor.start(); } public synchronized void addEventListener(ServerEventListener listener) { @@ -75,62 +51,6 @@ public class ForgeEventForwarder { public synchronized void removeAllListeners() { this.eventListeners.clear(); } - - @SubscribeEvent - public void onBlockBreak(BlockEvent.BreakEvent evt) { - onBlockChange(evt); - } - - @SubscribeEvent - public void onBlockPlace(BlockEvent.EntityPlaceEvent evt) { - onBlockChange(evt); - } - - private synchronized void onBlockChange(BlockEvent evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - Vector3i position = new Vector3i( - evt.getPos().getX(), - evt.getPos().getY(), - evt.getPos().getZ() - ); - - for (ServerEventListener listener : eventListeners) listener.onBlockChange(world, position); - - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - @SubscribeEvent - public synchronized void onChunkSave(ChunkDataEvent.Save evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - Vector2i chunkPos = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z); - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - for (ServerEventListener listener : eventListeners) listener.onChunkSaveToDisk(world, chunkPos); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - /* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk - @SubscribeEvent - public synchronized void onWorldSave(WorldEvent.Save evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world); - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - */ @SubscribeEvent public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) { @@ -143,64 +63,5 @@ public class ForgeEventForwarder { UUID uuid = evt.getPlayer().getUniqueID(); for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid); } - - @SubscribeEvent - public void onChunkLoad(ChunkEvent.Load evt) { - if (!(evt.getWorld() instanceof ServerWorld)) return; - - try { - UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld()); - Vector2i chunk = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z); - - synchronized (loadChunkEvents) { - loadChunkEvents.add(new WorldChunk(world, chunk)); - loadChunkEvents.notify(); - } - } catch (IOException e) { - Logger.global.noFloodError("Failed to get the UUID for a world!", e); - } - } - - /** - * This is a workaround for forge not providing a way to detect if chunks are newly generated: - * Each time a chunk-load-event occurs, it is (asynchronously) tested if the chunk is already generated on the world files. - * If it is a new chunk it will likely not be saved to the disk right away. - */ - private void processLoadChunkEvents() { - while (!Thread.interrupted()) { - WorldChunk worldChunk; - if (mod.getPlugin().isLoaded() && (worldChunk = loadChunkEvents.poll()) != null) { - try { - World world = mod.getPlugin().getWorld(worldChunk.world); - if (world == null || world.isChunkGenerated(worldChunk.chunk)) continue; - - for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(worldChunk.world, worldChunk.chunk); - - } catch (RuntimeException e) { - Logger.global.noFloodWarning("processLoadChunkEventsError", "Failed to test if a chunk is newly generated:" + e); - } - } else { - synchronized (loadChunkEvents) { - try { - loadChunkEvents.wait(10000); - } catch (InterruptedException e) { - break; - } - } - } - } - - Thread.currentThread().interrupt(); - } - - private class WorldChunk { - final UUID world; - final Vector2i chunk; - - public WorldChunk(UUID world, Vector2i chunk) { - this.world = world; - this.chunk = chunk; - } - } } diff --git a/implementations/forge-1.16.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java b/implementations/forge-1.16.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java index 24235d7e..3cfea37e 100644 --- a/implementations/forge-1.16.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java +++ b/implementations/forge-1.16.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java @@ -24,25 +24,8 @@ */ package de.bluecolored.bluemap.forge; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import net.minecraftforge.fml.event.server.FMLServerStartedEvent; -import org.apache.logging.log4j.LogManager; - import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; - import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.plugin.serverinterface.Player; @@ -64,8 +47,17 @@ import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.server.FMLServerStartedEvent; import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; +import org.apache.logging.log4j.LogManager; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; @Mod(Plugin.PLUGIN_ID) public class ForgeMod implements ServerInterface { @@ -88,10 +80,10 @@ public class ForgeMod implements ServerInterface { this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); - this.pluginInstance = new Plugin(MinecraftVersion.MC_1_16, "forge-1.16.2", this); + this.pluginInstance = new Plugin(new MinecraftVersion(1, 16, 2), "forge-1.16.2", this); this.worldUUIDs = new ConcurrentHashMap<>(); - this.eventForwarder = new ForgeEventForwarder(this); + this.eventForwarder = new ForgeEventForwarder(); this.worldUuidCache = Caffeine.newBuilder() .executor(BlueMap.THREAD_POOL) .weakKeys() diff --git a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf index ef3bb021..74d53aa7 100644 --- a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf +++ b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -2,4 +2,5 @@ liveUpdates: true skinDownload: true hiddenGameModes: [] hideInvisible: true -hideSneaking: false \ No newline at end of file +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin.conf index 67f297e2..2d3ec7e3 100644 --- a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin.conf +++ b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -24,4 +24,9 @@ hideInvisible: true # If this is true, players that are sneaking will be hidden on the map. # Default is false -hideSneaking: false \ No newline at end of file +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf index 2f276c92..3b775ae3 100644 --- a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf +++ b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -1,3 +1,4 @@ webroot: "bluemap/web" useCookies: true +enableFreeFlight: true maps: [] diff --git a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf index 1efd4978..4a602264 100644 --- a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "bluemap/web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/spigot/build.gradle b/implementations/spigot/build.gradle index 479efa46..61caed4e 100644 --- a/implementations/spigot/build.gradle +++ b/implementations/spigot/build.gradle @@ -11,9 +11,9 @@ repositories { dependencies { shadow "org.spigotmc:spigot-api:1.13-R0.1-SNAPSHOT" - compile group: 'org.bstats', name: 'bstats-bukkit-lite', version: '1.5' - - compile (project(':BlueMapCommon')) { + implementation group: 'org.bstats', name: 'bstats-bukkit', version: '2.2.1' + + implementation (project(':BlueMapCommon')) { //exclude dependencies provided by bukkit exclude group: 'com.google.guava', module: 'guava' exclude group: 'com.google.code.gson', module: 'gson' @@ -21,16 +21,16 @@ dependencies { } build.dependsOn shadowJar { - destinationDir = file '../../build/release' - archiveFileName = "BlueMap-${version}-spigot.jar" + destinationDirectory = file '../../build/release' + archiveFileName.set("BlueMap-${archiveVersion.get()}-spigot.jar") //relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'org.apache.commons.io', 'de.bluecolored.shadow.apache.commons.io' relocate 'org.apache.commons.lang3', 'de.bluecolored.shadow.apache.commons.lang3' - relocate 'org.bstats.bukkit', 'de.bluecolored.shadow.bstats.bukkit' + relocate 'org.bstats', 'de.bluecolored.shadow.bstats' relocate 'com.mojang.brigadier', 'de.bluecolored.shadow.mojang.brigadier' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' @@ -39,11 +39,13 @@ build.dependsOn shadowJar { relocate 'com.google.inject', 'de.bluecolored.shadow.google.inject' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } processResources { from(sourceSets.main.resources.srcDirs) { include 'plugin.yml' + duplicatesStrategy = DuplicatesStrategy.WARN expand ( version: project.version diff --git a/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java b/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java index 8013011f..53bedbe4 100644 --- a/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java +++ b/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java @@ -24,24 +24,14 @@ */ package de.bluecolored.bluemap.bukkit; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.bstats.bukkit.MetricsLite; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.serverinterface.Player; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; +import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; +import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.CommandMap; @@ -53,37 +43,40 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.java.JavaPlugin; -import de.bluecolored.bluemap.common.plugin.Plugin; -import de.bluecolored.bluemap.common.plugin.serverinterface.Player; -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; -import de.bluecolored.bluemap.core.MinecraftVersion; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class BukkitPlugin extends JavaPlugin implements ServerInterface, Listener { private static BukkitPlugin instance; - private Plugin pluginInstance; - private EventForwarder eventForwarder; - private BukkitCommands commands; + private final Plugin pluginInstance; + private final EventForwarder eventForwarder; + private final BukkitCommands commands; private int playerUpdateIndex = 0; - private Map onlinePlayerMap; - private List onlinePlayerList; + private final Map onlinePlayerMap; + private final List onlinePlayerList; public BukkitPlugin() { Logger.global = new JavaLogger(getLogger()); - MinecraftVersion version = MinecraftVersion.getLatest(); + MinecraftVersion version = MinecraftVersion.LATEST_SUPPORTED; //try to get best matching minecraft-version try { String versionString = getServer().getBukkitVersion(); - Matcher versionMatcher = Pattern.compile("(\\d+\\.\\d+\\.\\d+)[-_].*").matcher(versionString); + Matcher versionMatcher = Pattern.compile("(\\d+(?:\\.\\d+){1,2})[-_].*").matcher(versionString); if (!versionMatcher.matches()) throw new IllegalArgumentException(); - version = MinecraftVersion.fromVersionString(versionMatcher.group(1)); + version = MinecraftVersion.of(versionMatcher.group(1)); } catch (IllegalArgumentException e) { Logger.global.logWarning("Failed to detect the minecraft version of this server! Using latest version: " + version.getVersionString()); } @@ -152,8 +145,8 @@ public class BukkitPlugin extends JavaPlugin implements ServerInterface, Listene } }); - //init bstats - new MetricsLite(this); + //bstats + new Metrics(this, 5912); } @Override diff --git a/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/EventForwarder.java b/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/EventForwarder.java index c3a46ca8..2c020ac6 100644 --- a/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/EventForwarder.java +++ b/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/EventForwarder.java @@ -24,39 +24,21 @@ */ package de.bluecolored.bluemap.bukkit; -import java.util.ArrayList; -import java.util.Collection; -import java.util.UUID; - -import org.bukkit.Chunk; -import org.bukkit.Location; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.common.plugin.text.Text; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockBurnEvent; -import org.bukkit.event.block.BlockExplodeEvent; -import org.bukkit.event.block.BlockFadeEvent; -import org.bukkit.event.block.BlockFertilizeEvent; -import org.bukkit.event.block.BlockFormEvent; -import org.bukkit.event.block.BlockGrowEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.block.BlockSpreadEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.world.ChunkPopulateEvent; -import org.bukkit.event.world.ChunkUnloadEvent; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.common.plugin.text.Text; +import java.util.ArrayList; +import java.util.Collection; public class EventForwarder implements Listener { - private Collection listeners; + private final Collection listeners; public EventForwarder() { listeners = new ArrayList<>(); @@ -69,78 +51,6 @@ public class EventForwarder implements Listener { public synchronized void removeAllListeners() { listeners.clear(); } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public synchronized void onChunkSaveToDisk(ChunkUnloadEvent evt) { - Vector2i chunkPos = new Vector2i(evt.getChunk().getX(), evt.getChunk().getZ()); - for (ServerEventListener listener : listeners) listener.onChunkSaveToDisk(evt.getWorld().getUID(), chunkPos); - } - - /* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public synchronized void onWorldSaveToDisk(WorldSaveEvent evt) { - for (ServerEventListener listener : listeners) listener.onWorldSaveToDisk(evt.getWorld().getUID()); - } - */ - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockPlaceEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockBreakEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockGrowEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockBurnEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockExplodeEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockFadeEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockSpreadEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockFormEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockChange(BlockFertilizeEvent evt) { - onBlockChange(evt.getBlock().getLocation()); - } - - private synchronized void onBlockChange(Location loc) { - UUID world = loc.getWorld().getUID(); - Vector3i pos = new Vector3i(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); - for (ServerEventListener listener : listeners) listener.onBlockChange(world, pos); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public synchronized void onChunkFinishedGeneration(ChunkPopulateEvent evt) { - Chunk chunk = evt.getChunk(); - UUID world = chunk.getWorld().getUID(); - Vector2i chunkPos = new Vector2i(chunk.getX(), chunk.getZ()); - for (ServerEventListener listener : listeners) listener.onChunkFinishedGeneration(world, chunkPos); - } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public synchronized void onPlayerJoin(PlayerJoinEvent evt) { diff --git a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf index ef3bb021..74d53aa7 100644 --- a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf +++ b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -2,4 +2,5 @@ liveUpdates: true skinDownload: true hiddenGameModes: [] hideInvisible: true -hideSneaking: false \ No newline at end of file +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/plugin.conf index 67f297e2..2d3ec7e3 100644 --- a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/plugin.conf +++ b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -24,4 +24,9 @@ hideInvisible: true # If this is true, players that are sneaking will be hidden on the map. # Default is false -hideSneaking: false \ No newline at end of file +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render-defaults.conf index 2f276c92..3b775ae3 100644 --- a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render-defaults.conf +++ b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -1,3 +1,4 @@ webroot: "bluemap/web" useCookies: true +enableFreeFlight: true maps: [] diff --git a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render.conf index 8961e331..4a106542 100644 --- a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "bluemap/web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/spigot/src/main/resources/plugin.yml b/implementations/spigot/src/main/resources/plugin.yml index 29e5faa0..f37f7ff7 100644 --- a/implementations/spigot/src/main/resources/plugin.yml +++ b/implementations/spigot/src/main/resources/plugin.yml @@ -6,6 +6,7 @@ api-version: 1.13 author: "Blue (TBlueF / Lukas Rieger)" website: "https://github.com/BlueMap-Minecraft" commands: + permissions: bluemap.*: children: @@ -13,11 +14,13 @@ permissions: bluemap.version: true bluemap.help: true bluemap.reload: true - bluemap.pause: true - bluemap.resume: true - bluemap.render: true - bluemap.debug: true + bluemap.stop: true + bluemap.start: true + bluemap.freeze: true + bluemap.purge: true bluemap.marker: true + bluemap.update: true + bluemap.debug: true default: op bluemap.status: default: op @@ -27,13 +30,17 @@ permissions: default: op bluemap.reload: default: op - bluemap.pause: + bluemap.stop: default: op - bluemap.resume: + bluemap.start: default: op - bluemap.render: + bluemap.freeze: default: op - bluemap.debug: + bluemap.purge: default: op bluemap.marker: + default: op + bluemap.update: + default: op + bluemap.debug: default: op \ No newline at end of file diff --git a/implementations/sponge-7.2.0/build.gradle b/implementations/sponge-7.2.0/build.gradle index d4ecb5d6..0a1a29e7 100644 --- a/implementations/sponge-7.2.0/build.gradle +++ b/implementations/sponge-7.2.0/build.gradle @@ -1,9 +1,9 @@ dependencies { - shadow "org.spongepowered:spongeapi:7.3.0" - - compile group: 'org.bstats', name: 'bstats-sponge-lite', version: '1.5' - - compile (project(':BlueMapCommon')) { + shadow "org.spongepowered:spongeapi:7.2.0" + + implementation group: 'org.bstats', name: 'bstats-sponge', version: '2.2.1' + + implementation (project(':BlueMapCommon')) { //exclude dependencies provided by sponge exclude group: 'com.google.guava', module: 'guava' exclude group: 'com.google.code.gson', module: 'gson' @@ -15,24 +15,27 @@ dependencies { } build.dependsOn shadowJar { - destinationDir = file '../../build/release' - archiveFileName = "BlueMap-${version}-sponge-7.3.0.jar" + destinationDirectory = file '../../build/release' + archiveFileName.set("BlueMap-${archiveVersion.get()}-sponge-7.2.0.jar") relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' relocate 'org.apache.commons.io', 'de.bluecolored.shadow.apache.commons.io' relocate 'com.mojang.brigadier', 'de.bluecolored.shadow.mojang.brigadier' relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' - relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' + relocate 'org.bstats', 'de.bluecolored.shadow.bstats' relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' } processResources { from(sourceSets.main.resources.srcDirs) { include 'mcmod.info' + duplicatesStrategy = DuplicatesStrategy.WARN expand ( version: project.version diff --git a/implementations/sponge-7.2.0/src/main/java/de/bluecolored/bluemap/sponge/EventForwarder.java b/implementations/sponge-7.2.0/src/main/java/de/bluecolored/bluemap/sponge/EventForwarder.java index eee76e76..e033141c 100644 --- a/implementations/sponge-7.2.0/src/main/java/de/bluecolored/bluemap/sponge/EventForwarder.java +++ b/implementations/sponge-7.2.0/src/main/java/de/bluecolored/bluemap/sponge/EventForwarder.java @@ -24,65 +24,21 @@ */ package de.bluecolored.bluemap.sponge; -import java.util.Optional; - -import org.spongepowered.api.block.BlockSnapshot; -import org.spongepowered.api.data.Transaction; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; -import org.spongepowered.api.event.block.ChangeBlockEvent; -import org.spongepowered.api.event.filter.type.Exclude; -import org.spongepowered.api.event.message.MessageChannelEvent; -import org.spongepowered.api.event.network.ClientConnectionEvent; -import org.spongepowered.api.event.world.chunk.PopulateChunkEvent; -import org.spongepowered.api.event.world.chunk.SaveChunkEvent; -import org.spongepowered.api.world.Location; - -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; - import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.plugin.text.Text; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.message.MessageChannelEvent; +import org.spongepowered.api.event.network.ClientConnectionEvent; public class EventForwarder { - private ServerEventListener listener; + private final ServerEventListener listener; public EventForwarder(ServerEventListener listener) { this.listener = listener; } - /* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk - @Listener(order = Order.POST) - public void onWorldSaveToDisk(SaveWorldEvent evt) { - listener.onWorldSaveToDisk(evt.getTargetWorld().getUniqueId()); - } - */ - - @Listener(order = Order.POST) - public void onChunkSaveToDisk(SaveChunkEvent.Pre evt) { - listener.onChunkSaveToDisk(evt.getTargetChunk().getWorld().getUniqueId(), evt.getTargetChunk().getPosition().toVector2(true)); - } - - @Listener(order = Order.POST) - @Exclude({ChangeBlockEvent.Post.class, ChangeBlockEvent.Pre.class, ChangeBlockEvent.Modify.class}) - public void onBlockChange(ChangeBlockEvent evt) { - for (Transaction tr : evt.getTransactions()) { - if(!tr.isValid()) continue; - - Optional> ow = tr.getFinal().getLocation(); - if (ow.isPresent()) { - listener.onBlockChange(ow.get().getExtent().getUniqueId(), ow.get().getPosition().toInt()); - } - } - } - - @Listener(order = Order.POST) - public void onChunkFinishedGeneration(PopulateChunkEvent.Post evt) { - Vector3i chunkPos = evt.getTargetChunk().getPosition(); - listener.onChunkFinishedGeneration(evt.getTargetChunk().getWorld().getUniqueId(), new Vector2i(chunkPos.getX(), chunkPos.getZ())); - } - @Listener(order = Order.POST) public void onPlayerJoin(ClientConnectionEvent.Join evt) { listener.onPlayerJoin(evt.getTargetEntity().getUniqueId()); diff --git a/implementations/sponge-7.2.0/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java b/implementations/sponge-7.2.0/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java index 7f7f4afc..b47d0a1d 100644 --- a/implementations/sponge-7.2.0/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java +++ b/implementations/sponge-7.2.0/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java @@ -24,22 +24,17 @@ */ package de.bluecolored.bluemap.sponge; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import javax.inject.Inject; - -import org.bstats.sponge.MetricsLite2; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.serverinterface.Player; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; +import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; +import de.bluecolored.bluemap.sponge.SpongeCommands.SpongeCommandProxy; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.NBTUtil; +import org.bstats.sponge.Metrics; import org.spongepowered.api.Sponge; import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.event.Listener; @@ -54,16 +49,13 @@ import org.spongepowered.api.util.Tristate; import org.spongepowered.api.world.World; import org.spongepowered.api.world.storage.WorldProperties; -import de.bluecolored.bluemap.common.plugin.Plugin; -import de.bluecolored.bluemap.common.plugin.serverinterface.Player; -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; -import de.bluecolored.bluemap.core.MinecraftVersion; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; -import de.bluecolored.bluemap.sponge.SpongeCommands.SpongeCommandProxy; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.NBTUtil; +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; @org.spongepowered.api.plugin.Plugin ( id = Plugin.PLUGIN_ID, @@ -77,36 +69,35 @@ public class SpongePlugin implements ServerInterface { @ConfigDir(sharedRoot = false) private Path configurationDir; - @Inject - @SuppressWarnings("unused") - private MetricsLite2 metrics; - - private Plugin pluginInstance; - private SpongeCommands commands; + private final Plugin pluginInstance; + private final SpongeCommands commands; private SpongeExecutorService asyncExecutor; private SpongeExecutorService syncExecutor; private int playerUpdateIndex = 0; - private Map onlinePlayerMap; - private List onlinePlayerList; + private final Map onlinePlayerMap; + private final List onlinePlayerList; @Inject - public SpongePlugin(org.slf4j.Logger logger) { + public SpongePlugin(org.slf4j.Logger logger, Metrics.Factory metricsFactory) { Logger.global = new Slf4jLogger(logger); this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); - MinecraftVersion version = MinecraftVersion.MC_1_12; + MinecraftVersion version = new MinecraftVersion(1, 12); try { - version = MinecraftVersion.fromVersionString(Sponge.getPlatform().getMinecraftVersion().getName()); + version = MinecraftVersion.of(Sponge.getPlatform().getMinecraftVersion().getName()); } catch (IllegalArgumentException e) { Logger.global.logWarning("Failed to find a matching version for version-name '" + Sponge.getPlatform().getMinecraftVersion().getName() + "'! Using latest known sponge-version: " + version.getVersionString()); } - this.pluginInstance = new Plugin(version, "sponge", this); + this.pluginInstance = new Plugin(version, "sponge-7.2.0", this); this.commands = new SpongeCommands(pluginInstance); + + //bstats + metricsFactory.make(5911); } @Listener @@ -145,7 +136,7 @@ public class SpongePlugin implements ServerInterface { @Listener public void onServerStop(GameStoppingEvent evt) { Logger.global.logInfo("Stopping..."); - Sponge.getScheduler().getScheduledTasks(this).forEach(t -> t.cancel()); + Sponge.getScheduler().getScheduledTasks(this).forEach(Task::cancel); pluginInstance.unload(); Logger.global.logInfo("Saved and stopped!"); } @@ -207,9 +198,7 @@ public class SpongePlugin implements ServerInterface { @Override public String getWorldName(UUID worldUUID) { Optional world = Sponge.getServer().getWorld(worldUUID); - if (world.isPresent()) return world.get().getName(); - - return null; + return world.map(World::getName).orElse(null); } @Override @@ -233,7 +222,7 @@ public class SpongePlugin implements ServerInterface { if (pluginContainer != null) { Tristate metricsEnabled = Sponge.getMetricsConfigManager().getCollectionState(pluginContainer); if (metricsEnabled != Tristate.UNDEFINED) { - return metricsEnabled == Tristate.TRUE ? true : false; + return metricsEnabled == Tristate.TRUE; } } diff --git a/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf index 1fbe5b17..cb654cb8 100644 --- a/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf +++ b/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -2,4 +2,5 @@ liveUpdates: true skinDownload: false hiddenGameModes: [] hideInvisible: true -hideSneaking: false \ No newline at end of file +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/plugin.conf index a3d3a014..bf2feedc 100644 --- a/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/plugin.conf +++ b/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -24,4 +24,9 @@ hideInvisible: true # If this is true, players that are sneaking will be hidden on the map. # Default is false -hideSneaking: false \ No newline at end of file +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/render-defaults.conf index 2f276c92..3b775ae3 100644 --- a/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/render-defaults.conf +++ b/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -1,3 +1,4 @@ webroot: "bluemap/web" useCookies: true +enableFreeFlight: true maps: [] diff --git a/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/render.conf index 1efd4978..4a602264 100644 --- a/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/sponge-7.2.0/src/main/resources/de/bluecolored/bluemap/render.conf @@ -11,6 +11,10 @@ webroot: "bluemap/web" # Default is true useCookies: true +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + # This is an array with multiple configured maps. # You can define multiple maps, for different worlds with different render-settings here maps: [ diff --git a/implementations/sponge-7.2.0/src/main/resources/mcmod.info b/implementations/sponge-7.2.0/src/main/resources/mcmod.info index 5fa47f51..d30be9cb 100644 --- a/implementations/sponge-7.2.0/src/main/resources/mcmod.info +++ b/implementations/sponge-7.2.0/src/main/resources/mcmod.info @@ -9,10 +9,10 @@ "Blue (TBlueF, Lukas Rieger)" ], "dependencies": [ - "spongeapi@7.3.0" + "spongeapi@7.2.0" ], "requiredMods": [ - "spongeapi@7.3.0" + "spongeapi@7.2.0" ] } ] diff --git a/implementations/sponge-8.0.0/build.gradle b/implementations/sponge-8.0.0/build.gradle new file mode 100644 index 00000000..168ff897 --- /dev/null +++ b/implementations/sponge-8.0.0/build.gradle @@ -0,0 +1,44 @@ +dependencies { + shadow "org.spongepowered:spongeapi:8.0.0-SNAPSHOT" + + implementation group: 'org.bstats', name: 'bstats-sponge', version: '2.2.1' + + implementation (project(':BlueMapCommon')) { + //exclude dependencies provided by sponge + exclude group: 'com.google.guava', module: 'guava' + exclude group: 'com.google.code.gson', module: 'gson' + exclude group: 'org.apache.commons', module: 'commons-lang3' + exclude group: 'javax.inject' + exclude group: 'com.google.inject' + } +} + + +build.dependsOn shadowJar { + destinationDirectory = file '../../build/release' + archiveFileName.set("BlueMap-${archiveVersion.get()}-sponge-8.0.0.jar") + + relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt' + relocate 'org.apache.commons.io', 'de.bluecolored.shadow.apache.commons.io' + relocate 'com.mojang.brigadier', 'de.bluecolored.shadow.mojang.brigadier' + relocate 'com.github.benmanes.caffeine', 'de.bluecolored.shadow.benmanes.caffeine' + relocate 'com.google.errorprone', 'de.bluecolored.shadow.google.errorprone' + relocate 'org.spongepowered.configurate', 'de.bluecolored.shadow.configurate' + relocate 'org.aopalliance', 'de.bluecolored.shadow.aopalliance' + relocate 'org.bstats', 'de.bluecolored.shadow.bstats' + relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config' + relocate 'org.checkerframework', 'de.bluecolored.shadow.checkerframework' + relocate 'org.codehaus', 'de.bluecolored.shadow.codehaus' + relocate 'io.leangen.geantyref', 'de.bluecolored.shadow.geantyref' +} + +processResources { + from(sourceSets.main.resources.srcDirs) { + include 'META-INF/plugins.json' + duplicatesStrategy = DuplicatesStrategy.WARN + + expand ( + version: project.version + ) + } +} diff --git a/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/EventForwarder.java b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/EventForwarder.java new file mode 100644 index 00000000..4751e8b9 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/EventForwarder.java @@ -0,0 +1,58 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.sponge8; + +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.common.plugin.text.Text; +import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.message.PlayerChatEvent; +import org.spongepowered.api.event.network.ServerSideConnectionEvent; + +public class EventForwarder { + + private ServerEventListener listener; + + public EventForwarder(ServerEventListener listener) { + this.listener = listener; + } + + @Listener(order = Order.POST) + public void onPlayerJoin(ServerSideConnectionEvent.Join evt) { + listener.onPlayerJoin(evt.player().uniqueId()); + } + + @Listener(order = Order.POST) + public void onPlayerLeave(ServerSideConnectionEvent.Disconnect evt) { + listener.onPlayerJoin(evt.player().uniqueId()); + } + + @Listener(order = Order.POST) + public void onPlayerChat(PlayerChatEvent evt) { + listener.onChatMessage(Text.of(PlainComponentSerializer.plain().serialize(evt.message()))); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/WorldTile.java b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/Log4J2Logger.java similarity index 60% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/WorldTile.java rename to implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/Log4J2Logger.java index 3393a79c..c4cc35ed 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/WorldTile.java +++ b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/Log4J2Logger.java @@ -22,48 +22,47 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.render; +package de.bluecolored.bluemap.sponge8; -import java.util.Objects; +import de.bluecolored.bluemap.core.logger.AbstractLogger; +import org.apache.logging.log4j.Logger; -import com.flowpowered.math.vector.Vector2i; +public class Log4J2Logger extends AbstractLogger { -import de.bluecolored.bluemap.core.world.World; - -public class WorldTile { - - private World world; - private Vector2i tile; + private Logger out; - private int hash; - - public WorldTile(World world, Vector2i tile) { - this.world = world; - this.tile = tile; - - this.hash = Objects.hash(world.getUUID(), tile); + public Log4J2Logger(Logger out) { + this.out = out; } - public World getWorld() { - return world; + @Override + public void logError(String message, Throwable throwable) { + out.error(message, throwable); } - public Vector2i getTile() { - return tile; + @Override + public void logWarning(String message) { + out.warn(message); + } + + @Override + public void logInfo(String message) { + out.info(message); + } + + @Override + public void logDebug(String message) { + if (out.isDebugEnabled()) out.debug(message); } @Override - public boolean equals(Object obj) { - if (!(obj instanceof WorldTile)) return false; - WorldTile that = (WorldTile) obj; - - if (!this.world.getUUID().equals(that.world.getUUID())) return false; - return this.tile.equals(that.tile); + public void noFloodDebug(String message) { + if (out.isDebugEnabled()) super.noFloodDebug(message); } @Override - public int hashCode() { - return hash; + public void noFloodDebug(String key, String message) { + if (out.isDebugEnabled()) super.noFloodDebug(key, message); } } diff --git a/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongeCommandSource.java b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongeCommandSource.java new file mode 100644 index 00000000..0f987cf0 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongeCommandSource.java @@ -0,0 +1,79 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.sponge8; + +import com.flowpowered.math.vector.Vector3d; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; +import de.bluecolored.bluemap.common.plugin.text.Text; +import de.bluecolored.bluemap.core.world.World; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.spongepowered.api.service.permission.Subject; +import org.spongepowered.api.world.Locatable; + +import java.util.Optional; + +public class SpongeCommandSource implements CommandSource { + + private final Plugin plugin; + private final Audience audience; + private final Subject subject; + + public SpongeCommandSource(Plugin plugin, Audience audience, Subject subject) { + this.plugin = plugin; + this.subject = subject; + this.audience = audience; + } + + @Override + public void sendMessage(Text text) { + audience.sendMessage(GsonComponentSerializer.gson().deserialize(text.toJSONString())); + } + + @Override + public boolean hasPermission(String permission) { + return subject.hasPermission(permission); + } + + @Override + public Optional getPosition() { + if (audience instanceof Locatable) { + return Optional.of(SpongePlugin.fromSpongePoweredVector(((Locatable) audience).location().position())); + } + + return Optional.empty(); + } + + @Override + public Optional getWorld() { + if (audience instanceof Locatable) { + return Optional.ofNullable(plugin.getWorld(((Locatable) audience).serverLocation().world().uniqueId())); + } + + return Optional.empty(); + } + +} diff --git a/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongeCommands.java b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongeCommands.java new file mode 100644 index 00000000..c41ff2d2 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongeCommands.java @@ -0,0 +1,161 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.sponge8; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.Message; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.tree.CommandNode; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.commands.Commands; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.spongepowered.api.command.Command; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.CommandCompletion; +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.parameter.ArgumentReader; + +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class SpongeCommands { + + private final CommandDispatcher dispatcher; + + public SpongeCommands(final Plugin plugin) { + this.dispatcher = new CommandDispatcher<>(); + + // register commands + new Commands<>(plugin, dispatcher, cause -> new SpongeCommandSource(plugin, cause.audience(), cause.subject())); + } + + public Collection getRootCommands(){ + Collection rootCommands = new ArrayList<>(); + + for (CommandNode node : this.dispatcher.getRoot().getChildren()) { + rootCommands.add(new SpongeCommandProxy(node.getName())); + } + + return rootCommands; + } + + public class SpongeCommandProxy implements Command.Raw { + + private String label; + + protected SpongeCommandProxy(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + @Override + public CommandResult process(CommandCause cause, ArgumentReader.Mutable arguments) { + String command = label; + if (!arguments.input().isEmpty()) { + command += " " + arguments.input(); + } + + try { + return CommandResult.builder().result(dispatcher.execute(command, cause)).build(); + } catch (CommandSyntaxException ex) { + cause.audience().sendMessage(Component.text(ex.getRawMessage().getString(), NamedTextColor.RED)); + + String context = ex.getContext(); + if (context != null) cause.audience().sendMessage(Component.text(context, NamedTextColor.GRAY)); + + return CommandResult.empty(); + } + } + + @Override + public List complete(CommandCause cause, ArgumentReader.Mutable arguments) { + String command = label; + if (!arguments.input().isEmpty()) { + command += " " + arguments.input(); + } + + List completions = new ArrayList<>(); + + try { + Suggestions suggestions = dispatcher.getCompletionSuggestions(dispatcher.parse(command, cause)).get(100, TimeUnit.MILLISECONDS); + for (Suggestion suggestion : suggestions.getList()) { + String text = suggestion.getText(); + + if (text.indexOf(' ') == -1) { + Message tooltip = suggestion.getTooltip(); + if (tooltip == null) { + completions.add(CommandCompletion.of(text)); + } else { + completions.add(CommandCompletion.of(text, Component.text(tooltip.getString()))); + } + } + } + } catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + } catch (ExecutionException | TimeoutException ignore) {} + + completions.sort(Comparator.comparing(CommandCompletion::completion)); + + return completions; + } + + @Override + public boolean canExecute(CommandCause cause) { + return true; + } + + @Override + public Optional shortDescription(CommandCause cause) { + return Optional.empty(); + } + + @Override + public Optional extendedDescription(CommandCause cause) { + return Optional.empty(); + } + + @Override + public Component usage(CommandCause cause) { + CommandNode node = dispatcher.getRoot().getChild(label); + if (node == null) return Component.text("/" + label); + + List lines = new ArrayList<>(); + for (String usageString : dispatcher.getSmartUsage(node, cause).values()) { + lines.add(Component.text("/" + label + " ", NamedTextColor.WHITE).append(Component.text(usageString, NamedTextColor.GRAY))); + } + + return Component.join(Component.newline(), lines); + } + } + +} diff --git a/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlayer.java b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlayer.java new file mode 100644 index 00000000..014b0c55 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlayer.java @@ -0,0 +1,138 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.sponge8; + +import com.flowpowered.math.vector.Vector3d; +import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode; +import de.bluecolored.bluemap.common.plugin.serverinterface.Player; +import de.bluecolored.bluemap.common.plugin.text.Text; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.effect.potion.PotionEffect; +import org.spongepowered.api.effect.potion.PotionEffectTypes; +import org.spongepowered.api.entity.living.player.gamemode.GameMode; +import org.spongepowered.api.entity.living.player.gamemode.GameModes; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; + +import java.util.*; + +public class SpongePlayer implements Player { + + private static final Map GAMEMODE_MAP = new HashMap<>(5); + static { + GAMEMODE_MAP.put(GameModes.ADVENTURE.get(), Gamemode.ADVENTURE); + GAMEMODE_MAP.put(GameModes.SURVIVAL.get(), Gamemode.SURVIVAL); + GAMEMODE_MAP.put(GameModes.CREATIVE.get(), Gamemode.CREATIVE); + GAMEMODE_MAP.put(GameModes.SPECTATOR.get(), Gamemode.SPECTATOR); + GAMEMODE_MAP.put(GameModes.NOT_SET.get(), Gamemode.SURVIVAL); + } + + private final UUID uuid; + private Text name; + private UUID world; + private Vector3d position; + private boolean online; + private boolean sneaking; + private boolean invisible; + private Gamemode gamemode; + + public SpongePlayer(UUID playerUUID) { + this.uuid = playerUUID; + update(); + } + + @Override + public UUID getUuid() { + return this.uuid; + } + + @Override + public Text getName() { + return this.name; + } + + @Override + public UUID getWorld() { + return this.world; + } + + @Override + public Vector3d getPosition() { + return this.position; + } + + @Override + public boolean isOnline() { + return this.online; + } + + @Override + public boolean isSneaking() { + return this.sneaking; + } + + @Override + public boolean isInvisible() { + return this.invisible; + } + + @Override + public Gamemode getGamemode() { + return this.gamemode; + } + + /** + * API access, only call on server thread! + */ + public void update() { + ServerPlayer player = Sponge.server().player(uuid).orElse(null); + if (player == null) { + this.online = false; + return; + } + + this.gamemode = GAMEMODE_MAP.get(player.get(Keys.GAME_MODE).orElse(GameModes.NOT_SET.get())); + if (this.gamemode == null) this.gamemode = Gamemode.SURVIVAL; + + boolean invis = player.get(Keys.VANISH).orElse(false); + if (!invis && player.get(Keys.IS_INVISIBLE).orElse(false)) invis = true; + if (!invis) { + Optional> effects = player.get(Keys.POTION_EFFECTS); + if (effects.isPresent()) { + for (PotionEffect effect : effects.get()) { + if (effect.type().equals(PotionEffectTypes.INVISIBILITY.get()) && effect.duration() > 0) invis = true; + } + } + } + this.invisible = invis; + + this.name = Text.of(player.name()); + this.online = player.isOnline(); + this.position = SpongePlugin.fromSpongePoweredVector(player.position()); + this.sneaking = player.get(Keys.IS_SNEAKING).orElse(false); + this.world = player.world().uniqueId(); + } + +} diff --git a/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlugin.java b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlugin.java new file mode 100644 index 00000000..fa976c4f --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlugin.java @@ -0,0 +1,315 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.sponge8; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3d; +import com.flowpowered.math.vector.Vector3i; +import com.google.inject.Inject; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.serverinterface.Player; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; +import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; +import de.bluecolored.bluemap.sponge8.SpongeCommands.SpongeCommandProxy; +import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.NBTUtil; +import org.spongepowered.api.Platform; +import org.spongepowered.api.Server; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.Command; +import org.spongepowered.api.config.ConfigDir; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.lifecycle.RefreshGameEvent; +import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; +import org.spongepowered.api.event.lifecycle.StartedEngineEvent; +import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; +import org.spongepowered.api.event.network.ServerSideConnectionEvent; +import org.spongepowered.api.scheduler.ScheduledTask; +import org.spongepowered.api.scheduler.Task; +import org.spongepowered.api.util.Ticks; +import org.spongepowered.api.util.Tristate; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.plugin.PluginContainer; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +@org.spongepowered.plugin.jvm.Plugin(Plugin.PLUGIN_ID) +public class SpongePlugin implements ServerInterface { + + private final PluginContainer pluginContainer; + + @Inject + @ConfigDir(sharedRoot = false) + private Path configurationDir; + + private final Plugin pluginInstance; + private final SpongeCommands commands; + + private ExecutorService asyncExecutor; + private ExecutorService syncExecutor; + + private int playerUpdateIndex = 0; + private final Map onlinePlayerMap; + private final List onlinePlayerList; + + @Inject + public SpongePlugin(org.apache.logging.log4j.Logger logger, PluginContainer pluginContainer/*, Metrics.Factory metricsFactory*/) { + Logger.global = new Log4J2Logger(logger); + this.pluginContainer = pluginContainer; + + this.onlinePlayerMap = new ConcurrentHashMap<>(); + this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); + + final String versionFromSponge = Sponge.platform().container(Platform.Component.GAME).metadata().version(); + MinecraftVersion version = new MinecraftVersion(1, 16); + try { + version = MinecraftVersion.of(versionFromSponge); + } catch (IllegalArgumentException e) { + Logger.global.logWarning("Failed to find a matching version for version-name '" + versionFromSponge + "'! Using latest known sponge-version: " + version.getVersionString()); + } + + this.pluginInstance = new Plugin(version, "sponge-8.0.0", this); + this.commands = new SpongeCommands(pluginInstance); + + //bstats + //metricsFactory.make(5911); + } + + @Listener + public void onRegisterCommands(final RegisterCommandEvent event) { + //register commands + for(SpongeCommandProxy command : commands.getRootCommands()) { + event.register(this.pluginContainer, command, command.getLabel()); + } + + } + + @Listener + public void onServerStart(StartedEngineEvent evt) { + asyncExecutor = evt.game().asyncScheduler().createExecutor(pluginContainer); + syncExecutor = evt.engine().scheduler().createExecutor(pluginContainer); + + //start updating players + Task task = Task.builder() + .interval(Ticks.of(1)) + .execute(this::updateSomePlayers) + .plugin(pluginContainer) + .build(); + evt.engine().scheduler().submit(task); + + asyncExecutor.execute(() -> { + try { + Logger.global.logInfo("Loading..."); + pluginInstance.load(); + if (pluginInstance.isLoaded()) Logger.global.logInfo("Loaded!"); + } catch (IOException | ParseResourceException | RuntimeException e) { + Logger.global.logError("Failed to load!", e); + pluginInstance.unload(); + } + }); + } + + @Listener + public void onServerStop(StoppingEngineEvent evt) { + Logger.global.logInfo("Stopping..."); + evt.engine().scheduler().tasks(pluginContainer).forEach(ScheduledTask::cancel); + pluginInstance.unload(); + Logger.global.logInfo("Saved and stopped!"); + } + + @Listener + public void onServerReload(RefreshGameEvent evt) { + asyncExecutor.execute(() -> { + try { + Logger.global.logInfo("Reloading..."); + pluginInstance.reload(); + Logger.global.logInfo("Reloaded!"); + } catch (IOException | ParseResourceException | RuntimeException e) { + Logger.global.logError("Failed to load!", e); + pluginInstance.unload(); + } + }); + } + + @Listener + public void onPlayerJoin(ServerSideConnectionEvent.Join evt) { + SpongePlayer player = new SpongePlayer(evt.player().uniqueId()); + onlinePlayerMap.put(evt.player().uniqueId(), player); + onlinePlayerList.add(player); + } + + @Listener + public void onPlayerLeave(ServerSideConnectionEvent.Disconnect evt) { + UUID playerUUID = evt.player().uniqueId(); + onlinePlayerMap.remove(playerUUID); + synchronized (onlinePlayerList) { + onlinePlayerList.removeIf(p -> p.getUuid().equals(playerUUID)); + } + } + + @Override + public void registerListener(ServerEventListener listener) { + Sponge.eventManager().registerListeners(this.pluginContainer, new EventForwarder(listener)); + } + + @Override + public void unregisterAllListeners() { + Sponge.eventManager().unregisterPluginListeners(this.pluginContainer); + Sponge.eventManager().registerListeners(this.pluginContainer, this); + } + + @Override + public UUID getUUIDForWorld(File worldFolder) throws IOException { + try { + CompoundTag levelSponge = (CompoundTag) NBTUtil.readTag(new File(worldFolder, "level.dat")); + CompoundTag spongeData = levelSponge.getCompoundTag("SpongeData"); + int[] uuidIntArray = spongeData.getIntArray("UUID"); + if (uuidIntArray.length != 4) throw new IOException("World-UUID is stored in a wrong format! Is the worlds level.dat corrupted?"); + return intArrayToUuid(uuidIntArray); + } catch (IOException | RuntimeException e) { + throw new IOException("Failed to read the worlds level.dat!", e); + } + } + + @Override + public String getWorldName(UUID worldUUID) { + return getServerWorld(worldUUID) + .flatMap( + serverWorld -> serverWorld + .properties() + .displayName() + .map(PlainComponentSerializer.plain()::serialize) + ) + .orElse(null); + } + + private Optional getServerWorld(UUID worldUUID) { + for (ServerWorld world : Sponge.server().worldManager().worlds()) { + if (world.uniqueId().equals(worldUUID)) return Optional.of(world); + } + + return Optional.empty(); + } + + @Override + public File getConfigFolder() { + return configurationDir.toFile(); + } + + @Override + public Collection getOnlinePlayers() { + return onlinePlayerMap.values(); + } + + @Override + public Optional getPlayer(UUID uuid) { + return Optional.ofNullable(onlinePlayerMap.get(uuid)); + } + + @Override + public boolean isMetricsEnabled(boolean configValue) { + if (pluginContainer != null) { + Tristate metricsEnabled = Sponge.metricsConfigManager().collectionState(pluginContainer); + if (metricsEnabled != Tristate.UNDEFINED) { + return metricsEnabled == Tristate.TRUE; + } + } + + return Sponge.metricsConfigManager().globalCollectionState().asBoolean(); + } + + @Override + public boolean persistWorldChanges(UUID worldUUID) throws IOException, IllegalArgumentException { + try { + return syncExecutor.submit(() -> { + ServerWorld world = getServerWorld(worldUUID).orElse(null); + if (world == null) throw new IllegalArgumentException("There is no world with this uuid: " + worldUUID); + world.save(); + + return true; + }).get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException(e); + } catch (ExecutionException e) { + Throwable t = e.getCause(); + if (t instanceof IOException) throw (IOException) t; + if (t instanceof IllegalArgumentException) throw (IllegalArgumentException) t; + throw new IOException(t); + } + } + + /** + * Only update some of the online players each tick to minimize performance impact on the server-thread. + * Only call this method on the server-thread. + */ + private void updateSomePlayers() { + int onlinePlayerCount = onlinePlayerList.size(); + if (onlinePlayerCount == 0) return; + + int playersToBeUpdated = onlinePlayerCount / 20; //with 20 tps, each player is updated once a second + if (playersToBeUpdated == 0) playersToBeUpdated = 1; + + for (int i = 0; i < playersToBeUpdated; i++) { + playerUpdateIndex++; + if (playerUpdateIndex >= 20 && playerUpdateIndex >= onlinePlayerCount) playerUpdateIndex = 0; + + if (playerUpdateIndex < onlinePlayerCount) { + onlinePlayerList.get(playerUpdateIndex).update(); + } + } + } + + public static Vector3d fromSpongePoweredVector(org.spongepowered.math.vector.Vector3d vec) { + return new Vector3d(vec.x(), vec.y(), vec.z()); + } + + public static Vector3i fromSpongePoweredVector(org.spongepowered.math.vector.Vector3i vec) { + return new Vector3i(vec.x(), vec.y(), vec.z()); + } + + public static Vector2i fromSpongePoweredVector(org.spongepowered.math.vector.Vector2i vec) { + return new Vector2i(vec.x(), vec.y()); + } + + private static UUID intArrayToUuid(int[] array) { + if (array.length != 4) throw new IllegalArgumentException("Int array has to contain exactly 4 ints!"); + return new UUID( + (long) array[0] << 32 | (long) array[1] & 0x00000000FFFFFFFFL, + (long) array[2] << 32 | (long) array[3] & 0x00000000FFFFFFFFL + ); + } + +} diff --git a/implementations/sponge-8.0.0/src/main/resources/META-INF/plugins.json b/implementations/sponge-8.0.0/src/main/resources/META-INF/plugins.json new file mode 100644 index 00000000..599c8356 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/META-INF/plugins.json @@ -0,0 +1,31 @@ +{ + "plugins": [ + { + "loader": "java_plain", + "id": "bluemap", + "name": "BlueMap", + "version": "${version}", + "main-class": "de.bluecolored.bluemap.sponge8.SpongePlugin", + "description": "A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL)", + "links": { + "homepage": "https://bluecolo.red/bluemap", + "source": "https://github.com/BlueMap-Minecraft/BlueMap", + "issues": "https://github.com/BlueMap-Minecraft/BlueMap/issues" + }, + "contributors": [ + { + "name": "Blue (TBlueF, Lukas Rieger)", + "description": "Lead Developer" + } + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "8.0.0", + "load-order": "AFTER", + "optional": false + } + ] + } + ] +} diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/core-defaults.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/core-defaults.conf new file mode 100644 index 00000000..e51cf511 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/core-defaults.conf @@ -0,0 +1,4 @@ +accept-download: false +renderThreadCount: -2 +metrics: false +data: "bluemap" \ No newline at end of file diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/core.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/core.conf new file mode 100644 index 00000000..0b85eb35 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/core.conf @@ -0,0 +1,24 @@ +## ## +## BlueMap ## +## Core-Config ## +## ## + +# By changing the setting (accept-download) below to TRUE you are indicating that you have accepted mojang's EULA (https://account.mojang.com/documents/minecraft_eula), +# you confirm that you own a license to Minecraft (Java Edition) +# and you agree that BlueMap will download and use a minecraft-client file (depending on the minecraft-version) from mojangs servers (https://launcher.mojang.com/) for you. +# This file contains resources that belong to mojang and you must not redistribute it or do anything else that is not compliant with mojang's EULA. +# BlueMap uses resources in this file to generate the 3D-Models used for the map and texture them. (BlueMap will not work without those resources.) +# %datetime-iso% +accept-download: false + +# This changes the amount of threads that BlueMap will use to render the maps. +# A higher value can improve render-speed but could impact performance on the host machine. +# This should be always below or equal to the number of available processor-cores. +# Zero or a negative value means the amount of of available processor-cores subtracted by the value. +# (So a value of -2 with 6 cores results in 4 render-processes) +# Default is -2 +renderThreadCount: -2 + +# The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later. +# Default is "bluemap" +data: "bluemap" \ No newline at end of file diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf new file mode 100644 index 00000000..cb654cb8 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/plugin-defaults.conf @@ -0,0 +1,6 @@ +liveUpdates: true +skinDownload: false +hiddenGameModes: [] +hideInvisible: true +hideSneaking: false +fullUpdateInterval: 1440 \ No newline at end of file diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/plugin.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/plugin.conf new file mode 100644 index 00000000..bf2feedc --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/plugin.conf @@ -0,0 +1,32 @@ +## ## +## BlueMap ## +## Plugin-Config ## +## ## + +# If the server should send live-updates and player-positions. +# This only works if the integrated webserver is enabled. +# Default is true +liveUpdates: true + +# Download the skin from mojang-serves when a player joins your server, so it can be used for the player-markers. +# Default is false +skinDownload: false + +# A list of gamemodes that will prevent a player from appearing on the map. +# Possible values are: survival, creative, spectator, adventure +hiddenGameModes: [ + "spectator" +] + +# If this is true, players that have an invisibility (potion-)effect will be hidden on the map. +# Default is true +hideInvisible: true + +# If this is true, players that are sneaking will be hidden on the map. +# Default is false +hideSneaking: false + +# The interval in minutes in which a full map-update will be triggered. +# This is additionally!! to the normal map-update process (in case that fails to detect any file-changes). +# Default is 1440 (24 hours) +fullUpdateInterval: 1440 diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render-defaults.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render-defaults.conf new file mode 100644 index 00000000..3b775ae3 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render-defaults.conf @@ -0,0 +1,4 @@ +webroot: "bluemap/web" +useCookies: true +enableFreeFlight: true +maps: [] diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render.conf new file mode 100644 index 00000000..4a602264 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render.conf @@ -0,0 +1,130 @@ +## ## +## BlueMap ## +## Render-Config ## +## ## + +# The folder (webroot) where the map-data and web-application files will be saved. +# Default is "bluemap/web" +webroot: "bluemap/web" + +# If the web-application should use cookies to save the configurations of a user. +# Default is true +useCookies: true + +# If the free-flight-mode in the web-application is enabled or not. +# Default is true +enableFreeFlight: true + +# This is an array with multiple configured maps. +# You can define multiple maps, for different worlds with different render-settings here +maps: [ + + { + # The id of this map + # Should only contain word-charactes: [a-zA-Z0-9_] + # Changing this value breaks your existing renders. + id: "world" + + # 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: "World" + + # 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. + # You can change this at any time. + # This defaults to the world-spawn if you don't set it. + #startPos: [500, -820] + + # The color of thy sky as a hex-color + # You can change this at any time. + # Default is "#7dabff" + skyColor: "#7dabff" + + # Defines the ambient light-strength that every block is recieving, regardless of the sunlight/blocklight. + # 0 is no ambient light, 1 is fully lighted. + # You can change this at any time. + # Default is 0 + ambientLight: 0 + + # 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. + # 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. + # Changing this value requires a re-render of the map. + # Default is false + renderCaves: false + + # With the below values you can limit the map-render. + # This can be used to ignore the nethers ceiling or render only a certain part of a world. + # Changing this values might require a re-render of the map, already rendered tiles outside the limits will not be deleted. + # Default is no min or max value (= infinite bounds) + #minX: -4000 + #maxX: 4000 + #minZ: -4000 + #maxZ: 4000 + #minY: 50 + #maxY: 126 + + # Using this, BlueMap pretends that every Block out of the defined render-bounds is AIR, + # this means you can see the blocks where the world is cut (instead of having a see-through/xray view). + # This has only an effect if you set some render-bounds above. + # Changing this value requires a re-render of the map. + # Default is true + renderEdges: true + + # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. + # Files will be only 5% as big with compression! + # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. + # This is much better than disabling the compression. + # Changing this value requires a re-render of the map. + # Default is true + useCompression: true + + # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. + # If this is set to true BlueMap will render Chunks even if there is no light-data! + # This can be usefull for example if some mod prevents light-data from being saved correctly. + # However, this also has a few drawbacks: + # - For those chunks, every block will always be fully lit + # - Night-mode might not work correctly + # - Caves will always be rendered (ignoring the 'renderCaves' setting) + # Default is false + ignoreMissingLightData: false + } + + # Here another example for the End-Map + # Things we don't want to change from default we can just omit + { + id: "end" + name: "End" + world: "world/DIM1" + + # We dont want a blue sky in the end + skyColor: "#080010" + + # In the end is no sky-light, so we need to enable this or we won't see anything. + renderCaves: true + + # Same here, we don't want a dark map. But not completely lighted, so we see the effect of e.g torches. + ambientLight: 0.6 + } + + # Here another example for the Nether-Map + { + id: "nether" + name: "Nether" + world: "world/DIM-1" + + skyColor: "#290000" + + renderCaves: true + ambientLight: 0.6 + + # We slice the whole world at y:90 so every block above 90 will be air. + # This way we don't render the nethers ceiling. + maxY: 90 + renderEdges: true + } + +] diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/webserver-defaults.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/webserver-defaults.conf new file mode 100644 index 00000000..55e8c255 --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/webserver-defaults.conf @@ -0,0 +1,4 @@ +enabled: true +webroot: "bluemap/web" +port: 8100 +maxConnectionCount: 100 \ No newline at end of file diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/webserver.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/webserver.conf new file mode 100644 index 00000000..6af072df --- /dev/null +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/webserver.conf @@ -0,0 +1,29 @@ +## ## +## BlueMap ## +## Webserver-Config ## +## ## + +# With this setting you can disable the integrated web-server. +# This is usefull if you want to only render the map-data for later use, or if you setup your own webserver. +# Default is enabled +enabled: true + +# The webroot that the server will host to the web. +# Usually this should be set to the same directory like in the render.conf! +# Default is "bluemap/web" +webroot: "bluemap/web" + +# The IP-Adress that the webserver binds to. +# Use "0.0.0.0" to bind to all available local adresses. +# If you only want to access it locally use "localhost". +# Default is "0.0.0.0" +#ip: "localhost" +#ip: "123.45.6.78" + +# The port that the webserver listenes to. +# Default is 8100 +port: 8100 + +# Max number of simultaneous connections that the webserver allows +# Default is 100 +maxConnectionCount: 100 \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 5cbf959b..7002a9f2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,16 +17,20 @@ include ':BlueMapCore' include ':BlueMapCommon' include ':cli' + include ':sponge-7.2.0' +include ':sponge-8.0.0' + include ':spigot' -include ':forge-1.16.2' -include ':forge-1.15.2' include ':forge-1.14.4' +include ':forge-1.15.2' +include ':forge-1.16.2' -include ':fabric-1.16.2' -include ':fabric-1.16.1' include ':fabric-1.15.2' +include ':fabric-1.16.1' +include ':fabric-1.16.2' +include ':fabric-1.17' project(':BlueMapAPI').projectDir = "$rootDir/BlueMapAPI" as File @@ -34,13 +38,17 @@ project(':BlueMapCore').projectDir = "$rootDir/BlueMapCore" as File project(':BlueMapCommon').projectDir = "$rootDir/BlueMapCommon" as File project(':cli').projectDir = "$rootDir/implementations/cli" as File + project(':sponge-7.2.0').projectDir = "$rootDir/implementations/sponge-7.2.0" as File +project(':sponge-8.0.0').projectDir = "$rootDir/implementations/sponge-8.0.0" as File + project(':spigot').projectDir = "$rootDir/implementations/spigot" as File -project(':forge-1.16.2').projectDir = "$rootDir/implementations/forge-1.16.2" as File -project(':forge-1.15.2').projectDir = "$rootDir/implementations/forge-1.15.2" as File project(':forge-1.14.4').projectDir = "$rootDir/implementations/forge-1.14.4" as File +project(':forge-1.15.2').projectDir = "$rootDir/implementations/forge-1.15.2" as File +project(':forge-1.16.2').projectDir = "$rootDir/implementations/forge-1.16.2" as File -project(':fabric-1.16.2').projectDir = "$rootDir/implementations/fabric-1.16.2" as File -project(':fabric-1.16.1').projectDir = "$rootDir/implementations/fabric-1.16.1" as File project(':fabric-1.15.2').projectDir = "$rootDir/implementations/fabric-1.15.2" as File +project(':fabric-1.16.1').projectDir = "$rootDir/implementations/fabric-1.16.1" as File +project(':fabric-1.16.2').projectDir = "$rootDir/implementations/fabric-1.16.2" as File +project(':fabric-1.17').projectDir = "$rootDir/implementations/fabric-1.17" as File