mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-25 12:05:13 +01:00
Merge branch 'bleeding'
This commit is contained in:
commit
f66695acf6
4
.github/workflows/gradle.yml
vendored
4
.github/workflows/gradle.yml
vendored
@ -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
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 6bdbdb150db6ae0b2b4bf10fc9632e03367b310f
|
||||
Subproject commit c5450573abc12bb23fcdb470f7fda58ab1a0676a
|
@ -1 +1 @@
|
||||
Subproject commit 4bef101c6ee6ddef23049287cc1a3736d954979e
|
||||
Subproject commit df3676c5c9e58e41cb838b8c7c46309de5128246
|
@ -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 }
|
||||
}
|
||||
|
||||
|
@ -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 @@
|
||||
/**
|
||||
* 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<File, UUID, IOException> worldUUIDProvider;
|
||||
private ThrowingFunction<UUID, String, IOException> worldNameProvider;
|
||||
private final MinecraftVersion minecraftVersion;
|
||||
private final File configFolder;
|
||||
private final ThrowingFunction<File, UUID, IOException> worldUUIDProvider;
|
||||
private final ThrowingFunction<UUID, String, IOException> 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<UUID, World> worlds;
|
||||
private Map<String, MapType> maps;
|
||||
private Map<String, BmMap> maps;
|
||||
|
||||
public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) {
|
||||
this.minecraftVersion = minecraftVersion;
|
||||
@ -105,17 +104,17 @@ public synchronized void createOrUpdateWebApp(boolean force) throws IOException
|
||||
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 synchronized Map<UUID, World> getWorlds() throws IOException, Interrupted
|
||||
return worlds;
|
||||
}
|
||||
|
||||
public synchronized Map<String, MapType> getMaps() throws IOException, InterruptedException {
|
||||
public synchronized Map<String, BmMap> getMaps() throws IOException, InterruptedException {
|
||||
if (maps == null) loadWorldsAndMaps();
|
||||
return maps;
|
||||
}
|
||||
@ -135,6 +134,9 @@ public synchronized Map<String, MapType> getMaps() throws IOException, Interrupt
|
||||
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 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc
|
||||
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 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc
|
||||
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 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc
|
||||
|
||||
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 synchronized ResourcePack getResourcePack() throws IOException, Interrupt
|
||||
|
||||
//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 synchronized ResourcePack getResourcePack() throws IOException, Interrupt
|
||||
|
||||
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();
|
||||
|
@ -38,7 +38,7 @@ public InterruptableReentrantLock(boolean fair) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()) {
|
||||
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.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;
|
||||
}
|
||||
|
||||
}
|
@ -1,287 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.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<RenderTicket> renderTickets;
|
||||
private Map<RenderTicket, RenderTicket> renderTicketMap;
|
||||
private Deque<RenderTask> 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<RenderTicket> createTickets(MapType mapType, Collection<Vector2i> tiles) {
|
||||
if (tiles.size() < 0) return Collections.emptyList();
|
||||
|
||||
Collection<RenderTicket> 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<MapType, Vector2i> tileMap = MultimapBuilder.hashKeys().arrayListValues().<MapType, Vector2i>build();
|
||||
synchronized (renderTickets) {
|
||||
for (RenderTicket ticket : renderTickets) {
|
||||
tileMap.put(ticket.getMapType(), ticket.getTile());
|
||||
}
|
||||
}
|
||||
|
||||
//write renderTickets
|
||||
Set<MapType> maps = tileMap.keySet();
|
||||
out.writeInt(maps.size());
|
||||
for (MapType map : maps) {
|
||||
List<Vector2i> 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<MapType> 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<Vector2i> 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() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,217 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.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<Vector2i> 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<Vector2i> 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<Vector2i> 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<MapType> 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<Vector2i> 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;
|
||||
}
|
||||
|
||||
}
|
@ -27,7 +27,7 @@
|
||||
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 BlueMapAPIImpl(Plugin plugin) {
|
||||
}
|
||||
|
||||
maps = new HashMap<>();
|
||||
for (MapType map : plugin.getMapTypes()) {
|
||||
for (BmMap map : plugin.getMapTypes()) {
|
||||
BlueMapMapImpl m = new BlueMapMapImpl(this, map);
|
||||
maps.put(m.getId(), m);
|
||||
}
|
||||
|
@ -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 BlueMapWorldImpl getWorld() {
|
||||
|
||||
@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<Vector2i> filter) {
|
||||
delegate.setTileFilter(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<Vector2i> 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;
|
||||
}
|
||||
|
||||
|
@ -26,15 +26,16 @@
|
||||
|
||||
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 float getShapeMaxY() {
|
||||
|
||||
@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 Color getLineColor() {
|
||||
|
||||
@Override
|
||||
public synchronized void setLineColor(Color color) {
|
||||
Preconditions.checkNotNull(color);
|
||||
Objects.requireNonNull(color);
|
||||
|
||||
this.lineColor = color;
|
||||
this.hasUnsavedChanges = true;
|
||||
@ -135,7 +136,7 @@ public Color getFillColor() {
|
||||
|
||||
@Override
|
||||
public synchronized void setFillColor(Color color) {
|
||||
Preconditions.checkNotNull(color);
|
||||
Objects.requireNonNull(color);
|
||||
|
||||
this.fillColor = color;
|
||||
this.hasUnsavedChanges = true;
|
||||
@ -148,32 +149,32 @@ public void load(BlueMapAPI api, ConfigurationNode markerNode, boolean overwrite
|
||||
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<? extends ConfigurationNode> posNodes = node.getChildrenList();
|
||||
List<? extends ConfigurationNode> 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 @@ private Shape readShape(ConfigurationNode node) throws MarkerFileFormatException
|
||||
|
||||
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 @@ private static Vector2d readShapePos(ConfigurationNode node) throws MarkerFileFo
|
||||
|
||||
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 @@ private static Color readColor(ConfigurationNode node) throws MarkerFileFormatEx
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,7 +29,8 @@
|
||||
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 synchronized void load(BlueMapAPI api, ConfigurationNode markerNode, bool
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 Line getLine() {
|
||||
|
||||
@Override
|
||||
public synchronized void setLine(Line line) {
|
||||
Preconditions.checkNotNull(line);
|
||||
Objects.requireNonNull(line);
|
||||
|
||||
this.line = line;
|
||||
this.hasUnsavedChanges = true;
|
||||
@ -104,7 +105,7 @@ public Color getLineColor() {
|
||||
|
||||
@Override
|
||||
public synchronized void setLineColor(Color color) {
|
||||
Preconditions.checkNotNull(color);
|
||||
Objects.requireNonNull(color);
|
||||
|
||||
this.lineColor = color;
|
||||
this.hasUnsavedChanges = true;
|
||||
@ -117,27 +118,26 @@ public void load(BlueMapAPI api, ConfigurationNode markerNode, boolean overwrite
|
||||
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<? extends ConfigurationNode> posNodes = node.getChildrenList();
|
||||
List<? extends ConfigurationNode> 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 @@ private Line readLine(ConfigurationNode node) throws MarkerFileFormatException {
|
||||
|
||||
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 @@ private static Vector3d readLinePos(ConfigurationNode node) throws MarkerFileFor
|
||||
|
||||
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 @@ private static Color readColor(ConfigurationNode node) throws MarkerFileFormatEx
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 MarkerAPIImpl(BlueMapAPIImpl api, File markerFile) throws IOException {
|
||||
this.markerFile = markerFile;
|
||||
|
||||
this.markerSets = new ConcurrentHashMap<>();
|
||||
this.removedMarkerSets = Sets.newConcurrentHashSet();
|
||||
this.removedMarkerSets = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
load();
|
||||
}
|
||||
@ -93,63 +92,67 @@ public synchronized void load() throws IOException {
|
||||
}
|
||||
|
||||
private synchronized void load(boolean overwriteChanges) throws IOException {
|
||||
Set<String> externallyRemovedSets = new HashSet<>(markerSets.keySet());
|
||||
synchronized (MarkerAPIImpl.class) {
|
||||
Set<String> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 synchronized void load(BlueMapAPI api, ConfigurationNode markerNode, bool
|
||||
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 @@ private static Vector3d readPos(ConfigurationNode node) throws MarkerFileFormatE
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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.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 MarkerSetImpl(String id) {
|
||||
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 synchronized void load(BlueMapAPI api, ConfigurationNode node, boolean ov
|
||||
Line dummyLine = new Line(Vector3d.ZERO, Vector3d.ONE);
|
||||
|
||||
Set<String> 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 synchronized void load(BlueMapAPI api, ConfigurationNode node, boolean ov
|
||||
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();
|
||||
|
@ -28,7 +28,8 @@
|
||||
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 void load(BlueMapAPI api, ConfigurationNode markerNode, boolean overwrite
|
||||
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;
|
||||
}
|
||||
|
@ -30,7 +30,8 @@
|
||||
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 synchronized void load(BlueMapAPI api, ConfigurationNode markerNode, bool
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,15 +26,16 @@
|
||||
|
||||
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 float getShapeY() {
|
||||
|
||||
@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 Color getLineColor() {
|
||||
|
||||
@Override
|
||||
public synchronized void setLineColor(Color color) {
|
||||
Preconditions.checkNotNull(color);
|
||||
Objects.requireNonNull(color);
|
||||
|
||||
this.lineColor = color;
|
||||
this.hasUnsavedChanges = true;
|
||||
@ -127,7 +128,7 @@ public Color getFillColor() {
|
||||
|
||||
@Override
|
||||
public synchronized void setFillColor(Color color) {
|
||||
Preconditions.checkNotNull(color);
|
||||
Objects.requireNonNull(color);
|
||||
|
||||
this.fillColor = color;
|
||||
this.hasUnsavedChanges = true;
|
||||
@ -140,34 +141,34 @@ public void load(BlueMapAPI api, ConfigurationNode markerNode, boolean overwrite
|
||||
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<? extends ConfigurationNode> posNodes = node.getChildrenList();
|
||||
List<? extends ConfigurationNode> 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 @@ private Shape readShape(ConfigurationNode node) throws MarkerFileFormatException
|
||||
|
||||
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 @@ private static Vector2d readShapePos(ConfigurationNode node) throws MarkerFileFo
|
||||
|
||||
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 @@ private static Color readColor(ConfigurationNode node) throws MarkerFileFormatEx
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 void render(String mapId, Vector2i tile) {
|
||||
|
||||
@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<Vector2i> 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 boolean isRunning() {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,220 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.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<UUID, Vector2i> 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<Vector2i> chunks = updateBuffer.get(map.getWorld().getUUID());
|
||||
Collection<Vector2i> 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<Vector2i> chunks = updateBuffer.get(world);
|
||||
Collection<Vector2i> 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<Vector2i> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<UUID, World> worlds;
|
||||
private Map<String, MapType> maps;
|
||||
private Map<String, BmMap> maps;
|
||||
|
||||
private RenderManager renderManager;
|
||||
private WebServer webServer;
|
||||
|
||||
private final Timer daemonTimer;
|
||||
private TimerTask periodicalSaveTask;
|
||||
private TimerTask metricsTask;
|
||||
private Timer daemonTimer;
|
||||
|
||||
private Map<String, RegionFileWatchService> regionFileWatchServices;
|
||||
|
||||
private PluginConfig pluginConfig;
|
||||
private MapUpdateHandler updateHandler;
|
||||
private PlayerSkinUpdater skinUpdater;
|
||||
|
||||
private boolean loaded = false;
|
||||
@ -84,7 +98,7 @@ public Plugin(MinecraftVersion minecraftVersion, String implementationType, Serv
|
||||
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 void load() throws IOException, ParseResourceException {
|
||||
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 void load() throws IOException, ParseResourceException {
|
||||
//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 void run() {
|
||||
);
|
||||
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 void run() {
|
||||
}
|
||||
};
|
||||
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 void unload() {
|
||||
try {
|
||||
loadingLock.interruptAndLock();
|
||||
synchronized (this) {
|
||||
//save
|
||||
save();
|
||||
|
||||
//disable api
|
||||
if (api != null) api.unregister();
|
||||
@ -243,84 +292,116 @@ public void unload() {
|
||||
|
||||
//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 Collection<World> getWorlds(){
|
||||
return worlds.values();
|
||||
}
|
||||
|
||||
public Collection<MapType> getMapTypes(){
|
||||
public Collection<BmMap> 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 String getImplementationType() {
|
||||
public MinecraftVersion getMinecraftVersion() {
|
||||
return minecraftVersion;
|
||||
}
|
||||
|
||||
|
||||
private void initFileWatcherTasks() {
|
||||
for (BmMap map : maps.values()) {
|
||||
if (pluginState.getMapState(map).isUpdateEnabled()) {
|
||||
startWatchingMap(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<String> 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 boolean isHideInvisible() {
|
||||
public boolean isHideSneaking() {
|
||||
return this.hideSneaking;
|
||||
}
|
||||
|
||||
|
||||
public long getFullUpdateIntervalMinutes() {
|
||||
return fullUpdateIntervalMinutes;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<String, MapState> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<Vector2i, TimerTask> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public abstract class AbstractSuggestionProvider<S> implements SuggestionProvider<S> {
|
||||
|
||||
@Override
|
||||
|
@ -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<String, WeakReference<RenderTask>> taskRefMap;
|
||||
|
||||
public CommandHelper(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.taskRefMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public List<Text> createStatusMessage(){
|
||||
List<Text> lines = new ArrayList<>();
|
||||
|
||||
|
||||
RenderManager renderer = plugin.getRenderManager();
|
||||
|
||||
lines.add(Text.of());
|
||||
lines.add(Text.of(TextColor.BLUE, "Tile-Updates:"));
|
||||
List<RenderTask> 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<Vector2i> 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<Vector2i> 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<Vector2i> 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 Text worldHelperHover() {
|
||||
|
||||
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<RenderTask> 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<String> getTaskRefs() {
|
||||
return new ArrayList<>(taskRefMap.keySet());
|
||||
}
|
||||
|
||||
|
||||
private synchronized String getRefForTask(RenderTask task) {
|
||||
Iterator<Map.Entry<String, WeakReference<RenderTask>>> iterator = taskRefMap.entrySet().iterator();
|
||||
while (iterator.hasNext()){
|
||||
Map.Entry<String, WeakReference<RenderTask>> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<S> {
|
||||
|
||||
public static final String DEFAULT_MARKER_SET_ID = "markers";
|
||||
@ -136,66 +140,67 @@ public void init() {
|
||||
|
||||
.then(literal("cache")
|
||||
.executes(this::debugClearCacheCommand))
|
||||
|
||||
|
||||
.then(literal("dump")
|
||||
.executes(this::debugDumpCommand))
|
||||
|
||||
.build();
|
||||
|
||||
LiteralCommandNode<S> pauseCommand =
|
||||
literal("pause")
|
||||
.requires(requirements("bluemap.pause"))
|
||||
.executes(this::pauseCommand)
|
||||
LiteralCommandNode<S> stopCommand =
|
||||
literal("stop")
|
||||
.requires(requirements("bluemap.stop"))
|
||||
.executes(this::stopCommand)
|
||||
.build();
|
||||
|
||||
LiteralCommandNode<S> resumeCommand =
|
||||
literal("resume")
|
||||
.requires(requirements("bluemap.resume"))
|
||||
.executes(this::resumeCommand)
|
||||
LiteralCommandNode<S> startCommand =
|
||||
literal("start")
|
||||
.requires(requirements("bluemap.start"))
|
||||
.executes(this::startCommand)
|
||||
.build();
|
||||
|
||||
LiteralCommandNode<S> renderCommand =
|
||||
literal("render")
|
||||
.requires(requirements("bluemap.render"))
|
||||
.executes(this::renderCommand) // /bluemap render
|
||||
LiteralCommandNode<S> 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 <radius>
|
||||
LiteralCommandNode<S> 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 <x> <z> <radius>
|
||||
LiteralCommandNode<S> 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 <world|map>
|
||||
|
||||
.then(argument("x", DoubleArgumentType.doubleArg())
|
||||
.then(argument("z", DoubleArgumentType.doubleArg())
|
||||
.then(argument("radius", IntegerArgumentType.integer())
|
||||
.executes(this::renderCommand))))) // /bluemap render <world|map> <x> <z> <radius>
|
||||
.build();
|
||||
|
||||
LiteralCommandNode<S> prioRenderCommand =
|
||||
literal("prioritize")
|
||||
.requires(requirements("bluemap.render"))
|
||||
.then(argument("uuid", StringArgumentType.string())
|
||||
.executes(this::prioritizeRenderTaskCommand))
|
||||
.build();
|
||||
|
||||
LiteralCommandNode<S> cancelRenderCommand =
|
||||
literal("cancel")
|
||||
.requires(requirements("bluemap.render"))
|
||||
.executes(this::cancelLastRenderTaskCommand)
|
||||
|
||||
.then(argument("uuid", StringArgumentType.string())
|
||||
.executes(this::cancelRenderTaskCommand))
|
||||
|
||||
.build();
|
||||
LiteralCommandNode<S> updateCommand =
|
||||
addRenderArguments(
|
||||
literal("update")
|
||||
.requires(requirements("bluemap.update")),
|
||||
this::updateCommand
|
||||
).build();
|
||||
|
||||
LiteralCommandNode<S> 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<S> 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<S> worldsCommand =
|
||||
literal("worlds")
|
||||
@ -236,6 +241,12 @@ public void init() {
|
||||
.then(argument("id", StringArgumentType.word()).suggests(MarkerIdSuggestionProvider.getInstance())
|
||||
.executes(this::removeMarkerCommand))
|
||||
.build();
|
||||
|
||||
LiteralCommandNode<S> listMarkersCommand =
|
||||
literal("list")
|
||||
.requires(requirements("bluemap.marker"))
|
||||
.executes(this::listMarkersCommand)
|
||||
.build();
|
||||
|
||||
// command tree
|
||||
dispatcher.getRoot().addChild(baseCommand);
|
||||
@ -243,17 +254,41 @@ public void init() {
|
||||
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 extends ArgumentBuilder<S, B>> B addRenderArguments(B builder, Command<S> command) {
|
||||
return builder
|
||||
.executes(command) // /bluemap render
|
||||
|
||||
.then(argument("radius", IntegerArgumentType.integer())
|
||||
.executes(command)) // /bluemap render <radius>
|
||||
|
||||
.then(argument("x", DoubleArgumentType.doubleArg())
|
||||
.then(argument("z", DoubleArgumentType.doubleArg())
|
||||
.then(argument("radius", IntegerArgumentType.integer())
|
||||
.executes(command)))) // /bluemap render <x> <z> <radius>
|
||||
|
||||
.then(argument("world|map", StringArgumentType.string()).suggests(new WorldOrMapSuggestionProvider<>(plugin))
|
||||
.executes(command) // /bluemap render <world|map>
|
||||
|
||||
.then(argument("x", DoubleArgumentType.doubleArg())
|
||||
.then(argument("z", DoubleArgumentType.doubleArg())
|
||||
.then(argument("radius", IntegerArgumentType.integer())
|
||||
.executes(command))))); // /bluemap render <world|map> <x> <z> <radius>
|
||||
}
|
||||
|
||||
private Predicate<S> requirements(String permission){
|
||||
@ -296,8 +331,8 @@ private Optional<World> parseWorld(String worldName) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<MapType> parseMap(String mapId) {
|
||||
for (MapType map : plugin.getMapTypes()) {
|
||||
private Optional<BmMap> parseMap(String mapId) {
|
||||
for (BmMap map : plugin.getMapTypes()) {
|
||||
if (map.getId().equalsIgnoreCase(mapId)) {
|
||||
return Optional.of(map);
|
||||
}
|
||||
@ -306,14 +341,6 @@ private Optional<MapType> parseMap(String mapId) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<UUID> parseUUID(String uuidString) {
|
||||
try {
|
||||
return Optional.of(UUID.fromString(uuidString));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- COMMANDS ---
|
||||
|
||||
@ -334,21 +361,26 @@ public int versionCommand(CommandContext<S> context) {
|
||||
|
||||
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 int debugBlockCommand(CommandContext<S> context) {
|
||||
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 int debugBlockCommand(CommandContext<S> context) {
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int debugDumpCommand(CommandContext<S> 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<S> context) {
|
||||
public int stopCommand(CommandContext<S> 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<S> context) {
|
||||
public int startCommand(CommandContext<S> 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<S> context) {
|
||||
public int freezeCommand(CommandContext<S> 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<S> 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<S> context) {
|
||||
return updateCommand(context, true);
|
||||
}
|
||||
|
||||
public int updateCommand(CommandContext<S> context) {
|
||||
return updateCommand(context, false);
|
||||
}
|
||||
|
||||
public int updateCommand(CommandContext<S> context, boolean force) {
|
||||
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
// parse world/map argument
|
||||
Optional<String> 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 <world|map>")));
|
||||
if (worldToRender == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to update!").setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " <world|map>")));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -590,7 +732,7 @@ public int renderCommand(CommandContext<S> context) {
|
||||
} 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 <x> <z> " + radius)));
|
||||
source.sendMessage(Text.of(TextColor.RED, "Can't detect a position from this command-source, you'll have to define x,z coordinates to update with a radius!").setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " <x> <z> " + radius)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -603,13 +745,30 @@ public int renderCommand(CommandContext<S> context) {
|
||||
// execute render
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (world != null) {
|
||||
plugin.getServerInterface().persistWorldChanges(world.getUUID());
|
||||
helper.createWorldRenderTask(source, world, center, radius);
|
||||
List<BmMap> 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 int renderCommand(CommandContext<S> context) {
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int cancelCommand(CommandContext<S> context) {
|
||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
Optional<String> 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<RenderTask> 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<S> context) {
|
||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
@ -627,14 +814,34 @@ public int purgeCommand(CommandContext<S> context) {
|
||||
|
||||
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<BmMap> 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 int purgeCommand(CommandContext<S> context) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int prioritizeRenderTaskCommand(CommandContext<S> context) {
|
||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
String uuidString = context.getArgument("uuid", String.class);
|
||||
Optional<UUID> 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<S> 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<S> context) {
|
||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
String uuidString = context.getArgument("uuid", String.class);
|
||||
Optional<UUID> 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<S> context) {
|
||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
@ -721,8 +866,22 @@ public int mapsCommand(CommandContext<S> context) {
|
||||
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 int createMarkerCommand(CommandContext<S> context) {
|
||||
.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 int removeMarkerCommand(CommandContext<S> context) {
|
||||
source.sendMessage(Text.of(TextColor.GREEN, "Marker removed!"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int listMarkersCommand(CommandContext<S> 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<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
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<S> extends AbstractSuggestionProvider<S> {
|
||||
@ -42,7 +42,7 @@ public MapSuggestionProvider(Plugin plugin) {
|
||||
public Collection<String> getPossibleValues() {
|
||||
Collection<String> values = new HashSet<>();
|
||||
|
||||
for (MapType map : plugin.getMapTypes()) {
|
||||
for (BmMap map : plugin.getMapTypes()) {
|
||||
values.add(map.getId());
|
||||
}
|
||||
|
||||
|
@ -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<WorldSaveCallback> EVENT = EventFactory.createArrayBacked(WorldSaveCallback.class,
|
||||
(listeners) -> (world) -> {
|
||||
for (WorldSaveCallback event : listeners) {
|
||||
event.onWorldSaved(world);
|
||||
}
|
||||
}
|
||||
);
|
||||
public class TaskRefSuggestionProvider<S> extends AbstractSuggestionProvider<S> {
|
||||
|
||||
void onWorldSaved(ServerWorld world);
|
||||
private CommandHelper helper;
|
||||
|
||||
public TaskRefSuggestionProvider(CommandHelper helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPossibleValues() {
|
||||
return helper.getTaskRefs();
|
||||
}
|
||||
|
||||
}
|
@ -27,7 +27,7 @@
|
||||
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 Collection<String> getPossibleValues() {
|
||||
values.add(world.getName());
|
||||
}
|
||||
|
||||
for (MapType map : plugin.getMapTypes()) {
|
||||
for (BmMap map : plugin.getMapTypes()) {
|
||||
values.add(map.getId());
|
||||
}
|
||||
|
||||
|
@ -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) {};
|
||||
|
||||
|
@ -95,7 +95,7 @@ default boolean isMetricsEnabled(boolean configValue) {
|
||||
|
||||
/**
|
||||
* Returns the state of the player with that UUID if present<br>
|
||||
* 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<Player> getPlayer(UUID uuid);
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
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.UUID;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@DebugDump
|
||||
public class PlayerSkin {
|
||||
|
||||
private final UUID uuid;
|
||||
|
@ -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.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@DebugDump
|
||||
public class PlayerSkinUpdater implements ServerEventListener {
|
||||
|
||||
private File storageFolder;
|
||||
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<T extends RenderTask> implements RenderTask {
|
||||
|
||||
private final String description;
|
||||
private final List<T> tasks;
|
||||
private int currentTaskIndex;
|
||||
|
||||
public CombinedRenderTask(String description, Collection<T> 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<Path> 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();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<WorldRegionRenderTask> {
|
||||
|
||||
private final BmMap map;
|
||||
private final Collection<Vector2i> 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<Vector2i> regions) {
|
||||
this(map, regions, false);
|
||||
}
|
||||
|
||||
public MapUpdateTask(BmMap map, Collection<Vector2i> 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<Vector2i> getRegions() {
|
||||
return regions;
|
||||
}
|
||||
|
||||
private static Collection<WorldRegionRenderTask> createTasks(BmMap map, Collection<Vector2i> regions, boolean force) {
|
||||
List<WorldRegionRenderTask> 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<Vector2i> getRegions(World world) {
|
||||
return getRegions(world, null, -1);
|
||||
}
|
||||
|
||||
private static List<Vector2i> getRegions(World world, Vector2i center, int radius) {
|
||||
if (center == null || radius < 0) return new ArrayList<>(world.listRegions());
|
||||
|
||||
List<Vector2i> 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<Double> progressSupplier;
|
||||
private final int averagingCount;
|
||||
|
||||
private long lastTime;
|
||||
private double lastProgress;
|
||||
|
||||
private final Deque<Long> 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<Double> 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();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,347 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<WorkerThread> workerThreads;
|
||||
private final AtomicInteger busyCount;
|
||||
|
||||
private ProgressTracker progressTracker;
|
||||
private volatile boolean newTask;
|
||||
|
||||
@DebugDump private final LinkedList<RenderTask> 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<RenderTask> 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<RenderTask> 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<RenderTask> 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<RenderTask> 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<RenderTask> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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.<br>
|
||||
* 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();
|
||||
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<Vector2i> 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<Vector2l> 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<Vector2i> 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<WorldRegionRenderTask> 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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.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<? extends ConfigurationNode> 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<String> 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");
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<MinecraftVersion> {
|
||||
|
||||
private static final Pattern VERSION_REGEX = Pattern.compile("(?:(?<major>\\d+)\\.(?<minor>\\d+))(?:\\.(?<patch>\\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<MinecraftResource> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<? extends ConfigurationNode> autopoulationConfigLoader;
|
||||
private Map<Integer, Biome> biomes;
|
||||
private final ConfigurationLoader<? extends ConfigurationNode> autopoulationConfigLoader;
|
||||
private final Map<Integer, Biome> biomes;
|
||||
|
||||
public BiomeConfig(ConfigurationNode node) {
|
||||
this(node, null);
|
||||
@ -49,7 +49,7 @@ public BiomeConfig(ConfigurationNode node, ConfigurationLoader<? extends Configu
|
||||
|
||||
biomes = new ConcurrentHashMap<>(200, 0.5f, 8);
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> e : node.getChildrenMap().entrySet()){
|
||||
for (Entry<Object, ? extends ConfigurationNode> 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 Biome get(int id) {
|
||||
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);
|
||||
|
@ -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 BlockIdConfig(ConfigurationNode node, ConfigurationLoader<? extends Confi
|
||||
|
||||
this.lock = new ReentrantReadWriteLock();
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> e : node.getChildrenMap().entrySet()){
|
||||
for (Entry<Object, ? extends ConfigurationNode> e : node.childrenMap().entrySet()){
|
||||
String key = e.getKey().toString();
|
||||
String value = e.getValue().getString();
|
||||
|
||||
@ -119,7 +118,7 @@ public BlockState get(int numeralId, int meta) {
|
||||
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 BlockState get(String id, int numeralId, int meta) {
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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.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<? extends ConfigurationNode> autopoulationConfigLoader;
|
||||
private final ConfigurationLoader<? extends ConfigurationNode> autopoulationConfigLoader;
|
||||
|
||||
private Multimap<String, BlockStateMapping<BlockProperties>> mappings;
|
||||
private LoadingCache<BlockState, BlockProperties> mappingCache;
|
||||
private final Map<String, List<BlockStateMapping<BlockProperties>>> mappings;
|
||||
private final LoadingCache<BlockState, BlockProperties> 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<? extends ConfigurationNode> autopoulationConfigLoader) throws IOException {
|
||||
public BlockPropertiesConfig(ConfigurationNode node, ResourcePack resourcePack, ConfigurationLoader<? extends ConfigurationNode> autopoulationConfigLoader) {
|
||||
this.resourcePack = resourcePack;
|
||||
this.autopoulationConfigLoader = autopoulationConfigLoader;
|
||||
|
||||
mappings = new ConcurrentHashMap<>();
|
||||
|
||||
mappings = Multimaps.synchronizedListMultimap(MultimapBuilder.hashKeys().arrayListValues().build());
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> e : node.getChildrenMap().entrySet()){
|
||||
for (Entry<Object, ? extends ConfigurationNode> 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<BlockProperties> 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 BlockPropertiesConfig(ConfigurationNode node, ResourcePack resourcePack,
|
||||
mappingCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.maximumSize(10000)
|
||||
.build(key -> mapNoCache(key));
|
||||
.build(this::mapNoCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -91,7 +92,7 @@ public BlockProperties get(BlockState from){
|
||||
}
|
||||
|
||||
private BlockProperties mapNoCache(BlockState bs){
|
||||
for (BlockStateMapping<BlockProperties> bm : mappings.get(bs.getFullId())){
|
||||
for (BlockStateMapping<BlockProperties> bm : mappings.getOrDefault(bs.getFullId(), Collections.emptyList())){
|
||||
if (bm.fitsTo(bs)){
|
||||
return bm.getMapping();
|
||||
}
|
||||
@ -114,15 +115,15 @@ private BlockProperties mapNoCache(BlockState bs){
|
||||
} 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<BlockProperties>(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);
|
||||
|
@ -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.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 ConfigurationNode loadOrCreate(File configFile, URL defaultConfig, URL de
|
||||
} else {
|
||||
//create empty config
|
||||
ConfigurationLoader<? extends ConfigurationNode> loader = getLoader(configFile);
|
||||
configNode = loader.createEmptyNode();
|
||||
configNode = loader.createNode();
|
||||
|
||||
//save to create file
|
||||
if (generateEmptyConfig) loader.save(configNode);
|
||||
@ -109,7 +110,7 @@ public ConfigurationNode loadOrCreate(File configFile, URL defaultConfig, URL de
|
||||
//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 ConfigurationNode loadOrCreate(File configFile, URL defaultConfig, URL de
|
||||
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 void loadResourceConfigs(File configFolder, ResourcePack resourcePack) th
|
||||
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 void loadResourceConfigs(File configFolder, ResourcePack resourcePack) th
|
||||
);
|
||||
|
||||
//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 void loadResourceConfigs(File configFolder, ResourcePack resourcePack) th
|
||||
);
|
||||
|
||||
//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 @@ private ConfigurationNode joinFromResourcePack(ResourcePack resourcePack, String
|
||||
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 @@ private ConfigurationNode joinFromResourcePack(ResourcePack resourcePack, String
|
||||
|
||||
if (joinedNode == null) return defaultConfig;
|
||||
|
||||
joinedNode.mergeValuesFrom(defaultConfig);
|
||||
joinedNode.mergeFrom(defaultConfig);
|
||||
|
||||
return joinedNode;
|
||||
}
|
||||
@ -212,22 +213,22 @@ private ConfigurationNode joinFromResourcePack(ResourcePack resourcePack, String
|
||||
private ConfigurationLoader<? extends ConfigurationNode> 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<? extends ConfigurationNode> 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<? extends ConfigurationNode> 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)");
|
||||
|
@ -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"));
|
||||
|
||||
}
|
||||
|
||||
|
@ -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 boolean isRenderCaves() {
|
||||
public boolean isIgnoreMissingLightData() {
|
||||
return ignoreMissingLightData;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getHiresTileSize() {
|
||||
return hiresTileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLowresPointsPerHiresTile() {
|
||||
return lowresPointsPerHiresTile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLowresPointsPerLowresTile() {
|
||||
return lowresPointsPerLowresTile;
|
||||
}
|
||||
|
@ -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<MapConfig> 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 File getWebRoot() {
|
||||
public boolean isUseCookies() {
|
||||
return useCookies;
|
||||
}
|
||||
|
||||
|
||||
public boolean isEnableFreeFlight() {
|
||||
return enableFreeFlight;
|
||||
}
|
||||
|
||||
public List<MapConfig> getMapConfigs(){
|
||||
return mapConfigs;
|
||||
}
|
||||
|
@ -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 WebServerConfig(ConfigurationNode node) throws IOException {
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<WorldSaveCallback> 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);
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.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<Object> 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<Object> 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<Object> 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<String, String> 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.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<Vector2i> 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<Vector2i> getTileFilter() {
|
||||
return tileFilter;
|
||||
}
|
||||
|
||||
public void setTileFilter(Predicate<Vector2i> 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.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<Vector2i, Long> 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<Vector2i, Long> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<ChunkFinalizeCallback> 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);
|
||||
}
|
@ -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 Vector3i getBlockSize(){
|
||||
return blockSize;
|
||||
}
|
||||
|
||||
public Vector2i getTile(){
|
||||
return tile;
|
||||
}
|
||||
|
||||
}
|
@ -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 HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i ti
|
||||
/**
|
||||
* 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 @@ private void save(HiresModel model, String modelJson){
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tiles that the provided chunks are intersecting
|
||||
* Returns the tile-grid
|
||||
*/
|
||||
public Collection<Vector2i> getTilesForChunks(Iterable<Vector2i> chunks){
|
||||
Set<Vector2i> 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()));
|
||||
}
|
||||
|
||||
/**
|
@ -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 HiresModel render(WorldTile tile, AABB region) {
|
||||
}
|
||||
//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 HiresModel render(WorldTile tile, AABB region) {
|
||||
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;
|
@ -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 @@ default boolean useGzipCompression() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default RenderSettings copy() {
|
||||
return new StaticRenderSettings(
|
||||
isExcludeFacesWithoutSunlight(),
|
||||
getMin(),
|
||||
getMax(),
|
||||
isRenderEdges()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
@ -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.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<String> DEFAULT_WATERLOGGED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final HashSet<String> 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 BlockStateModel build(BlockModelResource bmr) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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.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 @@ private void createElementFace(BlockStateModel model, TransformedBlockModelResou
|
||||
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;
|
||||
}
|
||||
|
@ -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<Vector2i, LowresPoint> 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 void save(File file, boolean force, boolean useGzip) throws IOException {
|
||||
}
|
||||
|
||||
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 void flush(){
|
||||
if (changes.isEmpty()) return;
|
||||
|
||||
Map<Vector2i, LowresPoint> 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 BufferGeometry getBufferGeometry(){
|
||||
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;
|
@ -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 @@
|
||||
|
||||
public class LowresModelManager {
|
||||
|
||||
private Path fileRoot;
|
||||
|
||||
private Vector2i gridSize;
|
||||
private Vector2i pointsPerHiresTile;
|
||||
|
||||
private Map<File, CachedModel> models;
|
||||
|
||||
private boolean useGzip;
|
||||
private final Path fileRoot;
|
||||
private final Vector2i pointsPerLowresTile;
|
||||
private final Vector2i pointsPerHiresTile;
|
||||
private final boolean useGzip;
|
||||
|
||||
private final Map<File, CachedModel> 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 LowresModelManager(Path fileRoot, Vector2i gridSize, Vector2i pointsPerHi
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void render(HiresModel hiresModel) {
|
||||
* Saves all unsaved changes to the models to disk
|
||||
*/
|
||||
public synchronized void save(){
|
||||
for (CachedModel model : models.values()){
|
||||
saveModel(model);
|
||||
for (Entry<File, CachedModel> 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 @@ private LowresModel getModel(UUID world, Vector2i tile) {
|
||||
|
||||
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 @@ private LowresModel getModel(UUID world, Vector2i tile) {
|
||||
}
|
||||
|
||||
if (model == null){
|
||||
model = new CachedModel(world, tile, gridSize);
|
||||
model = new CachedModel(pointsPerLowresTile);
|
||||
}
|
||||
|
||||
models.put(modelFile, model);
|
||||
@ -227,18 +225,17 @@ public synchronized void tidyUpModelCache() {
|
||||
int size = entries.size();
|
||||
for (Entry<File, CachedModel> 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 @@ private synchronized void saveAndRemoveModel(CachedModel model) {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ private void saveModel(CachedModel model) {
|
||||
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();
|
||||
}
|
@ -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 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<String> 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<String> 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 boolean isGenerated() {
|
||||
|
||||
@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 BlockState getBlockState(Vector3i pos) {
|
||||
}
|
||||
|
||||
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 String getBlockIdMeta(Vector3i pos) {
|
||||
@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 LightData getLightData(Vector3i pos) {
|
||||
}
|
||||
|
||||
@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 BlockState getBlockState(Vector3i pos) {
|
||||
|
||||
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 String getBlockIdMeta(Vector3i pos) {
|
||||
}
|
||||
|
||||
int blockData = getByteHalf(this.data[blockHalfByteIndex], largeHalf);
|
||||
String forgeIdMapping = getWorld().getForgeBlockIdMapping(blockId);
|
||||
String forgeIdMapping = forgeBlockIdMapper.apply(blockId);
|
||||
|
||||
return blockId + ":" + blockData + " " + forgeIdMapping;
|
||||
}
|
||||
|
@ -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 boolean isGenerated() {
|
||||
|
||||
@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 BlockState getBlockState(Vector3i pos) {
|
||||
@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 LightData getLightData(Vector3i pos) {
|
||||
}
|
||||
|
||||
@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]);
|
||||
}
|
||||
|
||||
|
@ -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<CompoundTag>) levelData.getListTag("Sections"))) {
|
||||
@ -81,7 +79,7 @@ public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing
|
||||
}
|
||||
}
|
||||
else if (tag instanceof IntArrayTag) {
|
||||
biomes = ((IntArrayTag) tag).getValue();
|
||||
biomes = ((IntArrayTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (biomes == null || biomes.length == 0) {
|
||||
@ -100,7 +98,8 @@ public boolean isGenerated() {
|
||||
|
||||
@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 BlockState getBlockState(Vector3i pos) {
|
||||
@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 LightData getLightData(Vector3i 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;
|
||||
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;
|
||||
|
@ -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<Integer, Section> 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<CompoundTag>) 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 boolean isGenerated() {
|
||||
|
||||
@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 BlockState getBlockState(Vector3i pos) {
|
||||
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;
|
||||
|
@ -26,16 +26,13 @@
|
||||
|
||||
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 LightData getLightData(Vector3i pos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome getBiome(Vector3i pos) {
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return Biome.DEFAULT;
|
||||
}
|
||||
|
||||
|
@ -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 int getDataVersion() {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import 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<Vector2i> listChunks(long modifiedSince) {
|
||||
if (!regionFile.exists() || regionFile.length() == 0) return Collections.emptyList();
|
||||
|
||||
List<Vector2i> 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;
|
||||
}
|
||||
|
||||
}
|
@ -28,10 +28,9 @@
|
||||
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.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<Vector2i, MCARegion> regionCache;
|
||||
private final LoadingCache<Vector2i, MCAChunk> chunkCache;
|
||||
|
||||
private final LoadingCache<Vector2i, Chunk> chunkCache;
|
||||
|
||||
private BlockIdMapper blockIdMapper;
|
||||
private BlockPropertiesMapper blockPropertiesMapper;
|
||||
private BiomeMapper biomeMapper;
|
||||
|
||||
private final Multimap<String, BlockStateExtension> blockStateExtensions;
|
||||
private final Map<String, List<BlockStateExtension>> blockStateExtensions;
|
||||
|
||||
@DebugDump private boolean ignoreMissingLightData;
|
||||
|
||||
private boolean ignoreMissingLightData;
|
||||
|
||||
private Map<Integer, String> forgeBlockMappings;
|
||||
private final Map<Integer, String> 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 @@ private MCAWorld(
|
||||
this.worldFolder = worldFolder;
|
||||
this.minecraftVersion = minecraftVersion;
|
||||
this.name = name;
|
||||
this.seaLevel = seaLevel;
|
||||
this.spawnPoint = spawnPoint;
|
||||
|
||||
this.blockIdMapper = blockIdMapper;
|
||||
@ -100,7 +98,7 @@ private MCAWorld(
|
||||
|
||||
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 @@ private MCAWorld(
|
||||
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<Vector2i> getChunkList(long modifiedSinceMillis, Predicate<Vector2i> filter){
|
||||
List<Vector2i> 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<Vector2i> listRegions() {
|
||||
File[] regionFiles = getRegionFolder().toFile().listFiles();
|
||||
if (regionFiles == null) return Collections.emptyList();
|
||||
|
||||
List<Vector2i> regions = new ArrayList<>(regionFiles.length);
|
||||
|
||||
for (File file : regionFiles) {
|
||||
if (!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 Path getSaveFolder() {
|
||||
|
||||
@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 void invalidateChunkCache() {
|
||||
}
|
||||
|
||||
@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 @@ private Path getRegionFolder() {
|
||||
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 static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion versio
|
||||
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 static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion versio
|
||||
worldFolder,
|
||||
uuid,
|
||||
version,
|
||||
name,
|
||||
worldHeight,
|
||||
seaLevel,
|
||||
name,
|
||||
spawnPoint,
|
||||
blockIdMapper,
|
||||
blockPropertiesMapper,
|
||||
@ -469,22 +418,8 @@ public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion versio
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<String> 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 DoorExtension(MinecraftVersion version) {
|
||||
"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 DoorExtension(MinecraftVersion version) {
|
||||
"minecraft:jungle_door",
|
||||
"minecraft:acacia_door",
|
||||
"minecraft:dark_oak_door"
|
||||
);
|
||||
break;
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final Set<String> AFFECTED_BLOCK_IDS = new HashSet<>(Arrays.asList(
|
||||
"minecraft:chest",
|
||||
"minecraft:trapped_chest"
|
||||
);
|
||||
));
|
||||
|
||||
@Override
|
||||
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
|
||||
|
@ -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<String> 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;
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final Set<String> AFFECTED_BLOCK_IDS = new HashSet<>(Collections.singletonList(
|
||||
"minecraft:fire"
|
||||
);
|
||||
));
|
||||
|
||||
@Override
|
||||
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
|
||||
|
@ -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<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final HashSet<String> 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<String> getAffectedBlockIds() {
|
||||
|
@ -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<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final HashSet<String> AFFECTED_BLOCK_IDS = new HashSet<>(Collections.singletonList(
|
||||
"minecraft:nether_brick_fence"
|
||||
);
|
||||
));
|
||||
|
||||
@Override
|
||||
public Set<String> getAffectedBlockIds() {
|
||||
|
@ -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<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final Set<String> AFFECTED_BLOCK_IDS = new HashSet<>(Collections.singletonList(
|
||||
"minecraft:redstone_wire"
|
||||
);
|
||||
));
|
||||
|
||||
|
||||
private static final Set<String> CONNECTIBLE = Sets.newHashSet(
|
||||
private static final Set<String> 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) {
|
||||
|
@ -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<String> 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";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final Set<String> 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) {
|
||||
|
@ -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<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final HashSet<String> AFFECTED_BLOCK_IDS = new HashSet<>(Collections.singletonList(
|
||||
"minecraft:tripwire"
|
||||
);
|
||||
));
|
||||
|
||||
@Override
|
||||
public Set<String> getAffectedBlockIds() {
|
||||
|
@ -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<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
|
||||
private static final HashSet<String> AFFECTED_BLOCK_IDS = new HashSet<>(Arrays.asList(
|
||||
"minecraft:cobblestone_wall",
|
||||
"minecraft:mossy_cobblestone_wall"
|
||||
);
|
||||
));
|
||||
|
||||
@Override
|
||||
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
|
||||
|
@ -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<String> 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;
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.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 @@ private static String sendData(String data) throws MalformedURLException, IOExce
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
while ((line = in.readLine()) != null) {
|
||||
builder.append(line + "\n");
|
||||
builder.append(line).append("\n");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
|
@ -28,22 +28,21 @@
|
||||
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 Face(Vector3f p1, Vector3f p2, Vector3f p3, Vector2f uv1, Vector2f uv2, V
|
||||
|
||||
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 Face(Vector3f p1, Vector3f p2, Vector3f p3, Vector2f uv1, Vector2f uv2, V
|
||||
}
|
||||
|
||||
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 void setMaterialIndex(int materialIndex) {
|
||||
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;
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.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);
|
||||
}
|
||||
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.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;
|
||||
}
|
||||
|
||||
}
|
@ -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<String, Function<Block, Vector3f>> blockColorMap;
|
||||
private final Map<String, Function<Block, Vector3f>> blockColorMap;
|
||||
|
||||
public BlockColorCalculator(BufferedImage foliageMap, BufferedImage grassMap) {
|
||||
this.foliageMap = foliageMap;
|
||||
@ -60,12 +60,12 @@ public BlockColorCalculator(BufferedImage foliageMap, BufferedImage grassMap) {
|
||||
this.blockColorMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public void loadColorConfig(ConfigurationNode colorConfig) throws IOException {
|
||||
public void loadColorConfig(ConfigurationNode colorConfig) {
|
||||
blockColorMap.clear();
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : colorConfig.getChildrenMap().entrySet()){
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : colorConfig.childrenMap().entrySet()){
|
||||
String key = entry.getKey().toString();
|
||||
String value = entry.getValue().getString();
|
||||
String value = entry.getValue().getString("");
|
||||
|
||||
Function<Block, Vector3f> colorFunction;
|
||||
switch (value) {
|
||||
@ -200,7 +200,7 @@ public Biome next() {
|
||||
z++;
|
||||
}
|
||||
|
||||
return world.getBiome(new Vector3i(x, y, z));
|
||||
return world.getBiome(x, y, z);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -30,8 +30,8 @@
|
||||
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 @@ private BlockModelResource buildNoReset(String modelPath, boolean renderElements
|
||||
|
||||
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<Object, ? extends ConfigurationNode> entry : config.getNode("textures").getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> 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 @@ private BlockModelResource buildNoReset(String modelPath, boolean renderElements
|
||||
} 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 @@ private BlockModelResource buildNoReset(String modelPath, boolean renderElements
|
||||
}
|
||||
|
||||
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 @@ private BlockModelResource buildNoReset(String modelPath, boolean renderElements
|
||||
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 @@ private Face buildFace(Element element, Direction direction, ConfigurationNode n
|
||||
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 @@ private Face buildFace(Element element, Direction direction, ConfigurationNode n
|
||||
|
||||
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<? extends ConfigurationNode> nodeList = node.getChildrenList();
|
||||
List<? extends ConfigurationNode> 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 @@ private Vector3f readVector3f(ConfigurationNode node) throws ParseResourceExcept
|
||||
}
|
||||
|
||||
private Vector4f readVector4f(ConfigurationNode node) throws ParseResourceException {
|
||||
List<? extends ConfigurationNode> nodeList = node.getChildrenList();
|
||||
List<? extends ConfigurationNode> nodeList = node.childrenList();
|
||||
if (nodeList.size() < 4) throw new ParseResourceException("Failed to load Vector4: Not enough values in list-node!");
|
||||
|
||||
return new Vector4f(
|
||||
|
@ -26,13 +26,14 @@
|
||||
|
||||
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.Map.Entry;
|
||||
|
||||
public class BlockStateResource {
|
||||
|
||||
private List<Variant> variants = new ArrayList<>(0);
|
||||
private Collection<Variant> multipart = new ArrayList<>(0);
|
||||
|
||||
private static final MinecraftVersion NEW_MODEL_PATH_VERSION = new MinecraftVersion(1, 13);
|
||||
|
||||
private final List<Variant> variants = new ArrayList<>(0);
|
||||
private final Collection<Variant> multipart = new ArrayList<>(0);
|
||||
|
||||
private BlockStateResource() {
|
||||
}
|
||||
@ -90,7 +93,7 @@ public Collection<TransformedBlockModelResource> getModels(BlockState blockState
|
||||
return models;
|
||||
}
|
||||
|
||||
private class Variant {
|
||||
private static class Variant {
|
||||
|
||||
private PropertyCondition condition = PropertyCondition.all();
|
||||
private Collection<Weighted<TransformedBlockModelResource>> models = new ArrayList<>();
|
||||
@ -127,8 +130,8 @@ public void updateTotalWeight() {
|
||||
|
||||
private static class Weighted<T> {
|
||||
|
||||
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 BlockStateResource build(String blockstateFile) throws IOException {
|
||||
|
||||
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<Object, ? extends ConfigurationNode> entry : config.getNode("variants").getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : config.node("variants").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
try {
|
||||
@ -178,7 +181,7 @@ public BlockStateResource build(String blockstateFile) throws IOException {
|
||||
//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 BlockStateResource build(String blockstateFile) throws IOException {
|
||||
}
|
||||
|
||||
// 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 @@ private Collection<Weighted<TransformedBlockModelResource>> loadModels(Configura
|
||||
Collection<Weighted<TransformedBlockModelResource>> 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 @@ private Collection<Weighted<TransformedBlockModelResource>> loadModels(Configura
|
||||
}
|
||||
|
||||
private Weighted<TransformedBlockModelResource> loadModel(ConfigurationNode node, Map<String, String> 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 @@ private Weighted<TransformedBlockModelResource> loadModel(ConfigurationNode node
|
||||
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<TransformedBlockModelResource>(transformedModel, node.getNode("weight").getDouble(1d));
|
||||
return new Weighted<>(transformedModel, node.node("weight").getDouble(1d));
|
||||
}
|
||||
|
||||
private PropertyCondition parseCondition(ConfigurationNode conditionNode) {
|
||||
List<PropertyCondition> andConditions = new ArrayList<>();
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : conditionNode.getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : conditionNode.childrenMap().entrySet()) {
|
||||
String key = entry.getKey().toString();
|
||||
if (key.equals(JSON_COMMENT)) continue;
|
||||
|
||||
if (key.equals("OR")) {
|
||||
List<PropertyCondition> 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 @@ private PropertyCondition parseConditionString(String conditionString) throws Il
|
||||
} 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<ForgeVariant> variants = new ArrayList<>();
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : config.getNode("variants").getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : config.node("variants").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
if (isForgeStraightVariant(entry.getValue())) continue;
|
||||
|
||||
// create variants for single property
|
||||
List<ForgeVariant> propertyVariants = new ArrayList<>();
|
||||
String key = entry.getKey().toString();
|
||||
for (Entry<Object, ? extends ConfigurationNode> value : entry.getValue().getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> value : entry.getValue().childrenMap().entrySet()) {
|
||||
if (value.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
ForgeVariant variant = new ForgeVariant();
|
||||
@ -355,26 +355,26 @@ private BlockStateResource buildForge(ConfigurationNode config, String blockstat
|
||||
//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<String, String> textures = new HashMap<>();
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : modelNode.getNode("textures").getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> 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<PropertyCondition> conditions = new ArrayList<>(forgeVariant.properties.size());
|
||||
for (Entry<String, String> 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<Object, ? extends ConfigurationNode> entry : modelNode.getNode("submodel").getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> 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 @@ private BlockStateResource buildForge(ConfigurationNode config, String blockstat
|
||||
}
|
||||
|
||||
//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<String, String> textures = new HashMap<>();
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : normalNode.getNode("textures").getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> 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<Object, ? extends ConfigurationNode> entry : normalNode.getNode("submodel").getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> 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 @@ private boolean isForgeStraightVariant(ConfigurationNode node) {
|
||||
if (node.isList())
|
||||
return true;
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : node.getChildrenMap().entrySet()) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : node.childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
if (!entry.getValue().isMap()) return true;
|
||||
}
|
||||
@ -441,7 +441,7 @@ private boolean isForgeStraightVariant(ConfigurationNode node) {
|
||||
|
||||
private static class ForgeVariant {
|
||||
public Map<String, String> 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 ForgeVariant createMerge(ForgeVariant other) {
|
||||
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 @@ private static void logParseError(String message, Throwable throwable) {
|
||||
if (errorMessage == null) errorMessage = throwable.toString();
|
||||
Logger.global.logDebug(" > " + errorMessage);
|
||||
|
||||
//Logger.global.logError("DETAIL: ", throwable);
|
||||
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
}
|
||||
|
@ -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 boolean matches(BlockState state) {
|
||||
|
||||
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 boolean matches(BlockState state) {
|
||||
|
||||
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!");
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user