Compare commits
166 Commits
Author | SHA1 | Date |
---|---|---|
Antti Ellilä | 0f472a818d | |
Kyryl Andreiev | bfec68e414 | |
Lukas Rieger (Blue) | 7895222816 | |
Lukas Rieger (Blue) | cca1fbc826 | |
Lukas Rieger (Blue) | eeb01cba1a | |
Lukas Rieger (Blue) | 8f88dd7fd9 | |
Lukas Rieger (Blue) | 33d40b888a | |
Lukas Rieger (Blue) | 6392b67744 | |
Lukas Rieger | fbac48cd86 | |
Lukas Rieger (Blue) | 4a38a7491b | |
Lukas Rieger (Blue) | 2dfd9feb21 | |
Lukas Rieger (Blue) | 75b562eeb1 | |
Lukas Rieger (Blue) | 474c5e27c4 | |
Lukas Rieger (Blue) | d43c7c474f | |
Lukas Rieger (Blue) | 4b8245d19d | |
Lukas Rieger (Blue) | b5e8bf42ae | |
Lukas Rieger (Blue) | 7afcbeefd7 | |
Lukas Rieger (Blue) | 0cc0247930 | |
Lukas Rieger (Blue) | e04e46fa5f | |
Lukas Rieger (Blue) | b1c75aa44a | |
Lukas Rieger (Blue) | 02d9fc1405 | |
Lukas Rieger (Blue) | 52d1e59108 | |
Lukas Rieger (Blue) | 51185f5884 | |
Lukas Rieger (Blue) | 6ad50a89cb | |
Lukas Rieger (Blue) | 01b1ac513c | |
TechnicJelle | fc8377764c | |
Lukas Rieger (Blue) | a594a4bed3 | |
Lukas Rieger (Blue) | 2c2d2f9227 | |
Lukas Rieger (Blue) | 8455b50fc3 | |
Lukas Rieger (Blue) | 3db6833fc6 | |
Lukas Rieger (Blue) | 20aa0a72f5 | |
Lukas Rieger (Blue) | 3faf2f0135 | |
Lukas Rieger (Blue) | d77d90c658 | |
Lukas Rieger (Blue) | d7dd8931a5 | |
Lukas Rieger (Blue) | ce25eb52e3 | |
Lukas Rieger (Blue) | 93d8876b20 | |
Lukas Rieger (Blue) | 3cd3f1d032 | |
Lukas Rieger (Blue) | 0b111463be | |
Lukas Rieger (Blue) | b330c5d168 | |
Lukas Rieger (Blue) | 2029fe0a87 | |
Lukas Rieger (Blue) | 2777846cf8 | |
Lukas Rieger (Blue) | 1b26803527 | |
Lukas Rieger | 7cdc8213fa | |
Salzian | fe5c1fa785 | |
Lukas Rieger (Blue) | 909642d4c3 | |
Lukas Rieger (Blue) | 05bbd2b481 | |
Lukas Rieger (Blue) | 36c1d3f7ac | |
TyBraniff | a311fc1cef | |
Lukas Rieger (Blue) | 81fe41fd2b | |
Lukas Rieger (Blue) | a6402850c9 | |
Lukas Rieger (Blue) | 37dd18190b | |
Lukas Rieger (Blue) | fa966c4363 | |
Lukas Rieger (Blue) | 240ca6c00e | |
Lukas Rieger (Blue) | f18f7a9a16 | |
Lukas Rieger (Blue) | f66437ac83 | |
Lukas Rieger (Blue) | fdf242acdf | |
Lukas Rieger (Blue) | 7e7b1e4f53 | |
Lukas Rieger (Blue) | f097517320 | |
Lukas Rieger (Blue) | ee3ab6ff9a | |
Lukas Rieger (Blue) | 498a4f3190 | |
Lukas Rieger (Blue) | 757979b7b4 | |
Lukas Rieger (Blue) | 6e8247ae3a | |
Gerber Lóránt Viktor | a847e247e5 | |
Lukas Rieger (Blue) | 2689cd10e0 | |
Lukas Rieger (Blue) | b60b14372f | |
Gerber Lóránt Viktor | 10fb88df4b | |
Lukas Rieger (Blue) | 9fca7b9361 | |
Lukas Rieger (Blue) | a0e9180360 | |
Lukas Rieger (Blue) | e9e7042aed | |
Lukas Rieger (Blue) | b27aedc4c2 | |
Lukas Rieger (Blue) | 0613037093 | |
Lukas Rieger (Blue) | aecbd23ba7 | |
Lukas Rieger (Blue) | 2899646adc | |
Lukas Rieger (Blue) | c9a8c83d6e | |
Lukas Rieger (Blue) | ceb31b68eb | |
Lukas Rieger (Blue) | b625af695c | |
Lukas Rieger (Blue) | 908789a815 | |
Nikita | c0c946d154 | |
Lukas Rieger (Blue) | b02b91d3bb | |
Antti Ellilä | b437684dbb | |
Lukas Rieger (Blue) | 5bb7a77fb9 | |
Lukas Rieger (Blue) | 0d36a0f70b | |
Lukas Rieger (Blue) | 35c236e9ce | |
Lukas Rieger (Blue) | 79ea7baba7 | |
Lukas Rieger (Blue) | 122ba83ebb | |
Lukas Rieger (Blue) | d1aba560da | |
Lukas Rieger (Blue) | 9e8dc8e5a8 | |
Lukas Rieger (Blue) | ec101feb94 | |
Lukas Rieger (Blue) | 97c6640721 | |
Lukas Rieger (Blue) | 2c341fc894 | |
Lukas Rieger (Blue) | 40119127ee | |
Lukas Rieger (Blue) | 6e68a8f0e0 | |
Lukas Rieger (Blue) | 3a1e723a51 | |
Lukas Rieger (Blue) | dbde93c9f5 | |
Lukas Rieger (Blue) | ff1e38a7e1 | |
TechnicJelle | 2dd7a0a9c2 | |
Lukas Rieger (Blue) | cc50e05262 | |
Lukas Rieger (Blue) | 73a77e5e0e | |
Lukas Rieger (Blue) | 81e8da3b70 | |
Lukas Rieger (Blue) | e02a43a521 | |
Lukas Rieger (Blue) | 74c68c3428 | |
Lukas Rieger (Blue) | 584883444a | |
Lukas Rieger | 16981f2797 | |
Lukas Rieger (Blue) | efd45658d5 | |
Lukas Rieger (Blue) | b3c4f3737d | |
Lukas Rieger (Blue) | 61f883b134 | |
Lukas Rieger (Blue) | 6a10fac624 | |
Lukas Rieger (Blue) | 039a95c813 | |
jhqwqmc | b1aba76d09 | |
Lukas Rieger (Blue) | a703a92357 | |
Lukas Rieger (Blue) | 50b7f3670e | |
Lukas Rieger (Blue) | d6e4e69417 | |
Arkyoh | 6bf4291779 | |
Lukas Rieger (Blue) | 6ce32c56dc | |
birbkeks | e62919f14f | |
TechnicJelle | 7c3485363e | |
Lukas Rieger (Blue) | a0b47f1bd5 | |
Lukas Rieger (Blue) | ef728dee06 | |
Lukas Rieger (Blue) | 72b71a91e8 | |
Lukas Rieger (Blue) | 8104f99f60 | |
Lukas Rieger (Blue) | 78904b4051 | |
Lukas Rieger (Blue) | 6307fb1e6b | |
Lukas Rieger (Blue) | c7f340dec3 | |
Lukas Rieger (Blue) | 3f82a821a2 | |
Lukas Rieger (Blue) | 36090e8545 | |
Lukas Rieger (Blue) | 5866cb5766 | |
Albus Rex | 0bad1b6b2a | |
Lukas Rieger (Blue) | 5b93202994 | |
Lukas Rieger (Blue) | 20d32cb9c9 | |
Lukas Rieger (Blue) | 6f015da070 | |
Lukas Rieger (Blue) | deafe50305 | |
Lukas Rieger (Blue) | 73103eda3b | |
Lukas Rieger (Blue) | 77c1e42009 | |
Lukas Rieger (Blue) | 5f0942a8ae | |
Lukas Rieger (Blue) | 28c9166030 | |
Lukas Rieger (Blue) | c7af5e9639 | |
Lukas Rieger (Blue) | 6d3774f24e | |
Lukas Rieger | c69b31ce84 | |
Lukas Rieger | 665af5583f | |
Lukas Rieger (Blue) | 24a6b2f370 | |
Nikita | 609d6a90db | |
Lukas Rieger (Blue) | 1ae6fc790d | |
Lukas Rieger (Blue) | 5ac293ca18 | |
TechnicJelle | 48445db7b5 | |
Lukas Rieger (Blue) | e087f17564 | |
Lukas Rieger (Blue) | 808885b66a | |
Lukas Rieger (Blue) | a665fb377d | |
Lukas Rieger (Blue) | 15329b4f5d | |
Lukas Rieger (Blue) | eb258e7c33 | |
Lukas Rieger (Blue) | 4d38a2953d | |
Lukas Rieger (Blue) | 71fcd91c3e | |
Lukas Rieger | 01027af969 | |
TechnicJelle | 7047df73d0 | |
Lukas Rieger (Blue) | c407ba6bd5 | |
Lukas Rieger (Blue) | e1701c4754 | |
Lukas Rieger (Blue) | 4663eb715b | |
Lukas Rieger (Blue) | 7156993323 | |
YuRaNnNzZZ | aff64294af | |
Lukas Rieger (Blue) | 155f56e62a | |
Lukas Rieger (Blue) | 15d402cceb | |
Lukas Rieger (Blue) | 4aadaeb2e0 | |
Lukas Rieger (Blue) | 4386e35c59 | |
TWME | 87032fde81 | |
Lukas Rieger (Blue) | 9de49dc313 | |
Lukas Rieger (Blue) | 0eb12a1588 | |
Lukas Rieger (Blue) | eda3815b89 |
|
@ -4,6 +4,7 @@
|
|||
*.bat text eol=crlf
|
||||
gradlew text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.conf text eol=lf
|
||||
|
||||
*.java text
|
||||
*.java diff=java
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
import { execSync } from "node:child_process";
|
||||
import { readdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
// doesn't really matter, just setting something consistant and close enough for us europeans
|
||||
process.env.TZ = "Europe/Berlin";
|
||||
|
||||
function parse(str) {
|
||||
const blame = execSync(`git blame --porcelain ${str}`).toString("utf8").trim().split("\n");
|
||||
const commitMap = new Map();
|
||||
const nodes = [];
|
||||
const path = [];
|
||||
let inMultiLineString = false;
|
||||
let multiLineStringLastUpdated = null;
|
||||
// let multiLineStringValue = "";
|
||||
for (let i = 0; i < blame.length; i++) {
|
||||
const hash = blame[i].split(" ")[0];
|
||||
i++;
|
||||
if (!commitMap.has(hash)) {
|
||||
const commit = {};
|
||||
let j = 0;
|
||||
while (true) {
|
||||
const line = blame[i + j];
|
||||
if (line[0] === "\t") break;
|
||||
const [key, ...rest] = line.split(" ");
|
||||
const val = rest.join(" ");
|
||||
commit[key.replace(/-\S/g, (s) => s.slice(1).toUpperCase())] = val;
|
||||
j++;
|
||||
}
|
||||
commitMap.set(hash, commit);
|
||||
i += j;
|
||||
}
|
||||
const commit = commitMap.get(hash);
|
||||
let lastUpdated = parseInt(commit.authorTime);
|
||||
if (inMultiLineString) {
|
||||
const line = blame[i].slice(1).trimEnd();
|
||||
if (line.endsWith('"""')) {
|
||||
// multiLineStringValue += "\n" + line.slice(0, -3);
|
||||
nodes.push({
|
||||
path: [...path],
|
||||
lastUpdated: multiLineStringLastUpdated,
|
||||
// value: multiLineStringValue,
|
||||
});
|
||||
inMultiLineString = false;
|
||||
multiLineStringLastUpdated = null;
|
||||
// multiLineStringValue = "";
|
||||
path.pop();
|
||||
continue;
|
||||
} else {
|
||||
if (lastUpdated > multiLineStringLastUpdated)
|
||||
multiLineStringLastUpdated = commit.authorTime;
|
||||
// multiLineStringValue += "\n" + blame[i].slice(1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const line = blame[i].slice(1).trim();
|
||||
if (line === "{") continue;
|
||||
if (line === "}") {
|
||||
path.pop();
|
||||
continue;
|
||||
}
|
||||
if (!line.includes('"')) {
|
||||
path.push(line.split(":")[0].split(" ")[0]);
|
||||
continue;
|
||||
}
|
||||
const [key, rest] = line.split(":");
|
||||
if (rest.trimStart().startsWith('"""')) {
|
||||
inMultiLineString = true;
|
||||
multiLineStringLastUpdated = lastUpdated;
|
||||
// multiLineStringValue = rest.trimStart().slice(3);
|
||||
path.push(key);
|
||||
continue;
|
||||
}
|
||||
nodes.push({
|
||||
path: [...path, key],
|
||||
lastUpdated,
|
||||
// value: rest.trimStart().slice(1, -1)
|
||||
});
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
const langFolder = "../../BlueMapCommon/webapp/public/lang/";
|
||||
const languageFiles = readdirSync(langFolder).filter(
|
||||
(f) => f.endsWith(".conf") && f !== "settings.conf"
|
||||
);
|
||||
|
||||
const languages = languageFiles.map((file) => {
|
||||
const nodes = parse(path.join(langFolder, file));
|
||||
const name = file.split(".").reverse().slice(1).reverse().join(".");
|
||||
return {
|
||||
name,
|
||||
nodes,
|
||||
};
|
||||
});
|
||||
|
||||
const sourceLanguageName = "en";
|
||||
const sourceLanguage = languages.find((l) => l.name === sourceLanguageName);
|
||||
if (!sourceLanguage) throw new Error(`Source language "${sourceLanguageName}" not found!`);
|
||||
languages.splice(languages.indexOf(sourceLanguage), 1);
|
||||
|
||||
function diff(source, other) {
|
||||
const sourceKeys = source.map((n) => n.path.join("."));
|
||||
const otherKeys = other.map((n) => n.path.join("."));
|
||||
const missing = sourceKeys.filter((sk) => !otherKeys.includes(sk));
|
||||
const extra = otherKeys.filter((ok) => !sourceKeys.includes(ok));
|
||||
const outdated = other
|
||||
.map((n) => {
|
||||
const sourceNode = source.find((sn) => sn.path.join(".") === n.path.join("."));
|
||||
return { ...n, sourceNode };
|
||||
})
|
||||
.filter((n) => {
|
||||
return n.sourceNode && n.sourceNode.lastUpdated > n.lastUpdated;
|
||||
});
|
||||
return {
|
||||
missing,
|
||||
extra,
|
||||
outdated,
|
||||
};
|
||||
}
|
||||
|
||||
const upToDate = [];
|
||||
for (const { name, nodes } of languages) {
|
||||
const { missing, extra, outdated } = diff(sourceLanguage.nodes, nodes);
|
||||
|
||||
if (missing.length + extra.length + outdated.length === 0) {
|
||||
upToDate.push(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`=== ${name} ===`);
|
||||
if (missing.length) {
|
||||
console.log(`Missing (${missing.length}):`);
|
||||
for (const key of missing) console.log("-", key);
|
||||
console.log();
|
||||
}
|
||||
if (extra.length) {
|
||||
console.log(`Extra (${extra.length}):`);
|
||||
for (const key of extra) console.log("-", key);
|
||||
console.log();
|
||||
}
|
||||
if (outdated.length) {
|
||||
console.log(`Outdated (${outdated.length}):`);
|
||||
for (const { path, lastUpdated, sourceNode } of outdated)
|
||||
console.log(
|
||||
"-",
|
||||
path.join("."),
|
||||
`(updated ${new Date(lastUpdated * 1000).toLocaleString(
|
||||
"de"
|
||||
)}, source updated ${new Date(sourceNode.lastUpdated * 1000).toLocaleString("de")})`
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
if (upToDate.length) console.log("Up to date:", upToDate.join(", "));
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "translation-checker",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "translation-checker",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "translation-checker",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node ."
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -18,18 +19,15 @@ jobs:
|
|||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0 # needed for versioning
|
||||
- name: Set up JDK 1.17
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: "${{ runner.os }}-bluemap-${{ hashFiles('**/*.gradle*') }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bluemap-
|
||||
distribution: 'temurin'
|
||||
java-version: |
|
||||
16
|
||||
17
|
||||
21
|
||||
cache: 'gradle'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean spotlessCheck test build
|
||||
- uses: actions/upload-artifact@v2
|
||||
|
|
|
@ -2,29 +2,29 @@ name: Publish
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0 # needed for versioning
|
||||
- name: Set up JDK 1.17
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 17
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: "${{ runner.os }}-bluemap-${{ hashFiles('**/*.gradle*') }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bluemap-
|
||||
distribution: 'temurin'
|
||||
java-version: |
|
||||
16
|
||||
17
|
||||
21
|
||||
cache: 'gradle'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean :BlueMapCore:publish :BlueMapCommon:publish
|
||||
env:
|
||||
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
|
||||
run: ./gradlew publish
|
||||
BLUECOLORED_USERNAME: ${{ secrets.BLUECOLORED_USERNAME }}
|
||||
BLUECOLORED_PASSWORD: ${{ secrets.BLUECOLORED_PASSWORD }}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
name: Check translations
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "BlueMapCommon/webapp/public/lang/**"
|
||||
- ".github/translation-checker/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install deps
|
||||
working-directory: .github/translation-checker
|
||||
run: npm ci
|
||||
- name: Run Translation Checker
|
||||
working-directory: .github/translation-checker
|
||||
run: npm start
|
|
@ -14,9 +14,11 @@ node_modules/
|
|||
|
||||
*.launch
|
||||
|
||||
release.md
|
||||
|
||||
# exclude generated resource
|
||||
BlueMapCommon/src/main/resources/de/bluecolored/bluemap/webapp.zip
|
||||
BlueMapCore/src/main/resources/de/bluecolored/bluemap/*/resourceExtensions.zip
|
||||
BlueMapCore/src/main/resources/de/bluecolored/bluemap/resourceExtensions.zip
|
||||
|
||||
#exclude-test-data
|
||||
data/test-render
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit f398e7ab2bd77e582bce0a3848745031933d062f
|
||||
Subproject commit ec977113495dacd6f2e24239015f4b94b305fc52
|
|
@ -9,10 +9,11 @@ plugins {
|
|||
id ("com.github.node-gradle.node") version "3.5.0"
|
||||
}
|
||||
|
||||
group = "de.bluecolored.bluemap.common"
|
||||
version = "0.0.0"
|
||||
group = "de.bluecolored.bluemap"
|
||||
version = System.getProperty("bluemap.version") ?: "?" // set by BlueMapCore
|
||||
val lastVersion = System.getProperty("bluemap.lastVersion") ?: "?" // set by BlueMapCore
|
||||
|
||||
val javaTarget = 11
|
||||
val javaTarget = 16
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.toVersion(javaTarget)
|
||||
targetCompatibility = JavaVersion.toVersion(javaTarget)
|
||||
|
@ -20,19 +21,19 @@ java {
|
|||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
setUrl("https://libraries.minecraft.net")
|
||||
}
|
||||
maven {
|
||||
setUrl("https://jitpack.io")
|
||||
}
|
||||
maven ("https://libraries.minecraft.net")
|
||||
maven ("https://repo.bluecolored.de/releases")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api ("com.mojang:brigadier:1.0.17")
|
||||
api ("de.bluecolored.bluemap.core:BlueMapCore")
|
||||
|
||||
api ("de.bluecolored.bluemap:BlueMapCore")
|
||||
|
||||
compileOnly ("org.jetbrains:annotations:16.0.2")
|
||||
compileOnly ("org.projectlombok:lombok:1.18.32")
|
||||
|
||||
annotationProcessor ("org.projectlombok:lombok:1.18.32")
|
||||
|
||||
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
|
||||
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
||||
|
@ -69,12 +70,14 @@ tasks.test {
|
|||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
tasks.register("buildWebapp", type = NpmTask::class) {
|
||||
tasks.clean {
|
||||
doFirst {
|
||||
if (!file("webapp/dist/").deleteRecursively())
|
||||
throw IOException("Failed to delete build directory!")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("buildWebapp", type = NpmTask::class) {
|
||||
dependsOn ("npmInstall")
|
||||
args.set(listOf("run", "build"))
|
||||
|
||||
|
@ -88,7 +91,6 @@ tasks.register("zipWebapp", type = Zip::class) {
|
|||
archiveFileName.set("webapp.zip")
|
||||
destinationDirectory.set(file("src/main/resources/de/bluecolored/bluemap/"))
|
||||
|
||||
//outputs.upToDateWhen { false }
|
||||
inputs.dir("webapp/dist/")
|
||||
outputs.file("src/main/resources/de/bluecolored/bluemap/webapp.zip")
|
||||
}
|
||||
|
@ -99,6 +101,20 @@ tasks.processResources {
|
|||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "bluecolored"
|
||||
|
||||
val releasesRepoUrl = "https://repo.bluecolored.de/releases"
|
||||
val snapshotsRepoUrl = "https://repo.bluecolored.de/snapshots"
|
||||
url = uri(if (version == lastVersion) releasesRepoUrl else snapshotsRepoUrl)
|
||||
|
||||
credentials {
|
||||
username = project.findProperty("bluecoloredUsername") as String? ?: System.getenv("BLUECOLORED_USERNAME")
|
||||
password = project.findProperty("bluecoloredPassword") as String? ?: System.getenv("BLUECOLORED_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
create<MavenPublication>("maven") {
|
||||
groupId = project.group.toString()
|
||||
|
@ -106,6 +122,12 @@ publishing {
|
|||
version = project.version.toString()
|
||||
|
||||
from(components["java"])
|
||||
|
||||
versionMapping {
|
||||
usage("java-api") {
|
||||
fromResolutionOf("runtimeClasspath")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -1,5 +0,0 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -1,185 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MSYS* | MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -1,89 +0,0 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -26,10 +26,15 @@ package de.bluecolored.bluemap.common;
|
|||
|
||||
import de.bluecolored.bluemap.common.config.*;
|
||||
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public interface BlueMapConfigProvider {
|
||||
public interface BlueMapConfiguration {
|
||||
|
||||
@Nullable String getMinecraftVersion();
|
||||
|
||||
CoreConfig getCoreConfig();
|
||||
|
||||
WebappConfig getWebappConfig();
|
||||
|
@ -42,4 +47,8 @@ public interface BlueMapConfigProvider {
|
|||
|
||||
Map<String, StorageConfig> getStorageConfigs();
|
||||
|
||||
@Nullable Path getPacksFolder();
|
||||
|
||||
@Nullable Path getModsFolder();
|
||||
|
||||
}
|
|
@ -29,25 +29,24 @@ import com.google.gson.Gson;
|
|||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.api.gson.MarkerGson;
|
||||
import de.bluecolored.bluemap.api.markers.MarkerSet;
|
||||
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||
import de.bluecolored.bluemap.common.config.MapConfig;
|
||||
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.debug.StateDumper;
|
||||
import de.bluecolored.bluemap.common.debug.StateDumper;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.resources.VersionManifest;
|
||||
import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack;
|
||||
import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
|
@ -60,96 +59,43 @@ import java.lang.reflect.Type;
|
|||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 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 implements Closeable {
|
||||
|
||||
private final ServerInterface serverInterface;
|
||||
private final BlueMapConfigProvider configs;
|
||||
private final BlueMapConfiguration config;
|
||||
private final WebFilesManager webFilesManager;
|
||||
|
||||
private final Map<Path, String> worldIds;
|
||||
private MinecraftVersion minecraftVersion;
|
||||
private ResourcePack resourcePack;
|
||||
private final Map<String, World> worlds;
|
||||
private final Map<String, BmMap> maps;
|
||||
private final Map<String, Storage> storages;
|
||||
|
||||
private volatile WebFilesManager webFilesManager;
|
||||
|
||||
private Map<String, World> worlds;
|
||||
private Map<String, BmMap> maps;
|
||||
|
||||
private ResourcePack resourcePack;
|
||||
|
||||
|
||||
public BlueMapService(ServerInterface serverInterface, BlueMapConfigProvider configProvider, @Nullable ResourcePack preloadedResourcePack) {
|
||||
this(serverInterface, configProvider);
|
||||
|
||||
if (preloadedResourcePack != null)
|
||||
this.resourcePack = preloadedResourcePack;
|
||||
public BlueMapService(BlueMapConfiguration configuration, @Nullable ResourcePack preloadedResourcePack) {
|
||||
this(configuration);
|
||||
this.resourcePack = preloadedResourcePack;
|
||||
}
|
||||
|
||||
public BlueMapService(ServerInterface serverInterface, BlueMapConfigProvider configProvider) {
|
||||
this.serverInterface = serverInterface;
|
||||
this.configs = configProvider;
|
||||
public BlueMapService(BlueMapConfiguration configuration) {
|
||||
this.config = configuration;
|
||||
this.webFilesManager = new WebFilesManager(config.getWebappConfig().getWebroot());
|
||||
|
||||
this.worldIds = new ConcurrentHashMap<>();
|
||||
this.storages = new HashMap<>();
|
||||
this.worlds = new ConcurrentHashMap<>();
|
||||
this.maps = new ConcurrentHashMap<>();
|
||||
this.storages = new ConcurrentHashMap<>();
|
||||
|
||||
StateDumper.global().register(this);
|
||||
}
|
||||
|
||||
public String getWorldId(Path worldFolder) throws IOException {
|
||||
// fast-path
|
||||
String id = worldIds.get(worldFolder);
|
||||
if (id != null) return id;
|
||||
|
||||
// second try with normalized absolute path
|
||||
worldFolder = worldFolder.toAbsolutePath().normalize();
|
||||
id = worldIds.get(worldFolder);
|
||||
if (id != null) return id;
|
||||
|
||||
// secure (slower) query with real path
|
||||
worldFolder = worldFolder.toRealPath();
|
||||
id = worldIds.get(worldFolder);
|
||||
if (id != null) return id;
|
||||
|
||||
synchronized (worldIds) {
|
||||
// check again if another thread has already added the world
|
||||
id = worldIds.get(worldFolder);
|
||||
if (id != null) return id;
|
||||
|
||||
Logger.global.logDebug("Loading world id for '" + worldFolder + "'...");
|
||||
|
||||
// now we can be sure it wasn't loaded yet .. load
|
||||
Path idFile = worldFolder.resolve("bluemap.id");
|
||||
if (!Files.exists(idFile)) {
|
||||
id = this.serverInterface.getWorld(worldFolder)
|
||||
.flatMap(ServerWorld::getId)
|
||||
.orElse(UUID.randomUUID().toString());
|
||||
Files.writeString(idFile, id, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
|
||||
worldIds.put(worldFolder, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
id = Files.readString(idFile);
|
||||
worldIds.put(worldFolder, id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public WebFilesManager getWebFilesManager() {
|
||||
if (webFilesManager == null) {
|
||||
synchronized (this) {
|
||||
if (webFilesManager == null)
|
||||
webFilesManager = new WebFilesManager(configs.getWebappConfig().getWebroot());
|
||||
}
|
||||
}
|
||||
|
||||
return webFilesManager;
|
||||
}
|
||||
|
||||
|
@ -163,13 +109,13 @@ public class BlueMapService implements Closeable {
|
|||
}
|
||||
|
||||
// update settings.json
|
||||
if (!configs.getWebappConfig().isUpdateSettingsFile()) {
|
||||
if (!config.getWebappConfig().isUpdateSettingsFile()) {
|
||||
webFilesManager.loadSettings();
|
||||
webFilesManager.addFrom(configs.getWebappConfig());
|
||||
webFilesManager.addFrom(config.getWebappConfig());
|
||||
} else {
|
||||
webFilesManager.setFrom(configs.getWebappConfig());
|
||||
webFilesManager.setFrom(config.getWebappConfig());
|
||||
}
|
||||
for (String mapId : configs.getMapConfigs().keySet()) {
|
||||
for (String mapId : config.getMapConfigs().keySet()) {
|
||||
webFilesManager.addMap(mapId);
|
||||
}
|
||||
webFilesManager.saveSettings();
|
||||
|
@ -179,23 +125,45 @@ public class BlueMapService implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized Map<String, World> getWorlds() throws InterruptedException {
|
||||
if (worlds == null) loadWorldsAndMaps();
|
||||
return worlds;
|
||||
/**
|
||||
* Gets all loaded maps.
|
||||
* @return A map of loaded maps
|
||||
*/
|
||||
public Map<String, BmMap> getMaps() {
|
||||
return Collections.unmodifiableMap(maps);
|
||||
}
|
||||
|
||||
public synchronized Map<String, BmMap> getMaps() throws InterruptedException {
|
||||
if (maps == null) loadWorldsAndMaps();
|
||||
return maps;
|
||||
/**
|
||||
* Gets all loaded worlds.
|
||||
* @return A map of loaded worlds
|
||||
*/
|
||||
public Map<String, World> getWorlds() {
|
||||
return Collections.unmodifiableMap(worlds);
|
||||
}
|
||||
|
||||
private synchronized void loadWorldsAndMaps() throws InterruptedException {
|
||||
maps = new HashMap<>();
|
||||
worlds = new HashMap<>();
|
||||
/**
|
||||
* Gets or loads configured maps.
|
||||
* @return A map of loaded maps
|
||||
*/
|
||||
public Map<String, BmMap> getOrLoadMaps() throws InterruptedException {
|
||||
return getOrLoadMaps(mapId -> true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or loads configured maps.
|
||||
* @param filter A predicate filtering map-ids that should be loaded
|
||||
* (if maps are already loaded, they will be returned as well)
|
||||
* @return A map of all loaded maps
|
||||
*/
|
||||
public synchronized Map<String, BmMap> getOrLoadMaps(Predicate<String> filter) throws InterruptedException {
|
||||
for (var entry : config.getMapConfigs().entrySet()) {
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
if (!filter.test(entry.getKey())) continue;
|
||||
if (maps.containsKey(entry.getKey())) continue;
|
||||
|
||||
for (var entry : configs.getMapConfigs().entrySet()) {
|
||||
try {
|
||||
loadMapConfig(entry.getKey(), entry.getValue());
|
||||
loadMap(entry.getKey(), entry.getValue());
|
||||
} catch (ConfigurationException ex) {
|
||||
Logger.global.logWarning(ex.getFormattedExplanation());
|
||||
Throwable cause = ex.getRootCause();
|
||||
|
@ -204,16 +172,15 @@ public class BlueMapService implements Closeable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
worlds = Collections.unmodifiableMap(worlds);
|
||||
maps = Collections.unmodifiableMap(maps);
|
||||
return Collections.unmodifiableMap(maps);
|
||||
}
|
||||
|
||||
private synchronized void loadMapConfig(String id, MapConfig mapConfig) throws ConfigurationException, InterruptedException {
|
||||
private synchronized void loadMap(String id, MapConfig mapConfig) throws ConfigurationException, InterruptedException {
|
||||
String name = mapConfig.getName();
|
||||
if (name == null) name = id;
|
||||
|
||||
Path worldFolder = mapConfig.getWorld();
|
||||
Key dimension = mapConfig.getDimension();
|
||||
|
||||
// if there is no world configured, we assume the map is static, or supplied from a different server
|
||||
if (worldFolder == null) {
|
||||
|
@ -221,48 +188,63 @@ public class BlueMapService implements Closeable {
|
|||
return;
|
||||
}
|
||||
|
||||
// if there is no dimension configured, we assume world-folder is actually the dimension-folder and convert (backwards compatibility)
|
||||
if (dimension == null) {
|
||||
worldFolder = worldFolder.normalize();
|
||||
if (worldFolder.endsWith("DIM-1")) {
|
||||
worldFolder = worldFolder.getParent();
|
||||
dimension = DataPack.DIMENSION_THE_NETHER;
|
||||
} else if (worldFolder.endsWith("DIM1")) {
|
||||
worldFolder = worldFolder.getParent();
|
||||
dimension = DataPack.DIMENSION_THE_END;
|
||||
} else if (
|
||||
worldFolder.getNameCount() > 3 &&
|
||||
worldFolder.getName(worldFolder.getNameCount() - 3).toString().equals("dimensions")
|
||||
) {
|
||||
String namespace = worldFolder.getName(worldFolder.getNameCount() - 2).toString();
|
||||
String value = worldFolder.getName(worldFolder.getNameCount() - 1).toString();
|
||||
worldFolder = worldFolder.subpath(0, worldFolder.getNameCount() - 3);
|
||||
dimension = new Key(namespace, value);
|
||||
} else {
|
||||
dimension = DataPack.DIMENSION_OVERWORLD;
|
||||
}
|
||||
|
||||
Logger.global.logInfo("The map '" + name + "' has no dimension configured.\n" +
|
||||
"Assuming world: '" + worldFolder + "' and dimension: '" + dimension + "'.");
|
||||
}
|
||||
|
||||
if (!Files.isDirectory(worldFolder)) {
|
||||
throw new ConfigurationException(
|
||||
"'" + worldFolder.toAbsolutePath().normalize() + "' does not exist or is no directory!\n" +
|
||||
"Check if the 'world' setting in the config-file for that map is correct, or remove the entire config-file if you don't want that map.");
|
||||
}
|
||||
|
||||
String worldId;
|
||||
try {
|
||||
worldId = getWorldId(worldFolder);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
"Could not load the ID for the world (" + worldFolder.toAbsolutePath().normalize() + ")!\n" +
|
||||
"Make sure BlueMap has read and write access/permissions to the world-files for this map.",
|
||||
ex);
|
||||
}
|
||||
|
||||
String worldId = World.id(worldFolder, dimension);
|
||||
World world = worlds.get(worldId);
|
||||
if (world == null) {
|
||||
try {
|
||||
Logger.global.logInfo("Loading world '" + worldId + "' (" + worldFolder.toAbsolutePath().normalize() + ")...");
|
||||
world = new MCAWorld(worldFolder, mapConfig.getWorldSkyLight(), mapConfig.isIgnoreMissingLightData());
|
||||
Logger.global.logDebug("Loading world " + worldId + " ...");
|
||||
world = MCAWorld.load(worldFolder, dimension, loadDataPack(worldFolder));
|
||||
worlds.put(worldId, world);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
"Failed to load world '" + worldId + "' (" + worldFolder.toAbsolutePath().normalize() + ")!\n" +
|
||||
"Failed to load world " + worldId + "!\n" +
|
||||
"Is the level.dat of that world present and not corrupted?",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
Storage storage = getStorage(mapConfig.getStorage());
|
||||
Storage storage = getOrLoadStorage(mapConfig.getStorage());
|
||||
|
||||
try {
|
||||
|
||||
Logger.global.logInfo("Loading map '" + name + "'...");
|
||||
Logger.global.logInfo("Loading map '" + id + "'...");
|
||||
BmMap map = new BmMap(
|
||||
id,
|
||||
name,
|
||||
worldId,
|
||||
world,
|
||||
storage,
|
||||
getResourcePack(),
|
||||
storage.map(id),
|
||||
getOrLoadResourcePack(),
|
||||
mapConfig
|
||||
);
|
||||
maps.put(id, map);
|
||||
|
@ -293,18 +275,18 @@ public class BlueMapService implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized Storage getStorage(String storageId) throws ConfigurationException {
|
||||
public synchronized Storage getOrLoadStorage(String storageId) throws ConfigurationException, InterruptedException {
|
||||
Storage storage = storages.get(storageId);
|
||||
|
||||
if (storage == null) {
|
||||
try {
|
||||
StorageConfig storageConfig = getConfigs().getStorageConfigs().get(storageId);
|
||||
StorageConfig storageConfig = getConfig().getStorageConfigs().get(storageId);
|
||||
if (storageConfig == null) {
|
||||
throw new ConfigurationException("There is no storage-configuration for '" + storageId + "'!\n" +
|
||||
"You will either need to define that storage, or change the map-config to use a storage-config that exists.");
|
||||
}
|
||||
|
||||
Logger.global.logInfo("Initializing Storage: '" + storageId + "' (Type: " + storageConfig.getStorageType() + ")");
|
||||
Logger.global.logInfo("Initializing Storage: '" + storageId + "' (Type: '" + storageConfig.getStorageType().getKey() + "')");
|
||||
|
||||
storage = storageConfig.createStorage();
|
||||
storage.initialize();
|
||||
|
@ -331,130 +313,156 @@ public class BlueMapService implements Closeable {
|
|||
return storage;
|
||||
}
|
||||
|
||||
public synchronized ResourcePack getResourcePack() throws ConfigurationException, InterruptedException {
|
||||
public @Nullable ResourcePack getResourcePack() {
|
||||
return resourcePack;
|
||||
}
|
||||
|
||||
public synchronized ResourcePack getOrLoadResourcePack() throws ConfigurationException, InterruptedException {
|
||||
if (resourcePack == null) {
|
||||
MinecraftVersion minecraftVersion = serverInterface.getMinecraftVersion();
|
||||
MinecraftVersion minecraftVersion = getOrLoadMinecraftVersion();
|
||||
Path vanillaResourcePack = minecraftVersion.getResourcePack();
|
||||
|
||||
Path defaultResourceFile = configs.getCoreConfig().getData().resolve("minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar");
|
||||
Path resourceExtensionsFile = configs.getCoreConfig().getData().resolve("resourceExtensions.zip");
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
Path resourcePackFolder = serverInterface.getConfigFolder().resolve("resourcepacks");
|
||||
Deque<Path> packRoots = getPackRoots();
|
||||
packRoots.addLast(vanillaResourcePack);
|
||||
|
||||
try {
|
||||
FileHelper.createDirectories(resourcePackFolder);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
"BlueMap failed to create this folder:\n" +
|
||||
resourcePackFolder + "\n" +
|
||||
"Does BlueMap has sufficient permissions?",
|
||||
ex);
|
||||
}
|
||||
|
||||
if (!Files.exists(defaultResourceFile)) {
|
||||
if (configs.getCoreConfig().isAcceptDownload()) {
|
||||
//download file
|
||||
try {
|
||||
Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ...");
|
||||
|
||||
FileHelper.createDirectories(defaultResourceFile.getParent());
|
||||
Path tempResourceFile = defaultResourceFile.getParent().resolve(defaultResourceFile.getFileName() + ".filepart");
|
||||
Files.deleteIfExists(tempResourceFile);
|
||||
FileUtils.copyURLToFile(new URL(minecraftVersion.getResource().getClientUrl()), tempResourceFile.toFile(), 10000, 10000);
|
||||
FileHelper.move(tempResourceFile, defaultResourceFile);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException("Failed to download resources!", ex);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new MissingResourcesException();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(resourceExtensionsFile);
|
||||
FileHelper.createDirectories(resourceExtensionsFile.getParent());
|
||||
URL resourceExtensionsUrl = Objects.requireNonNull(
|
||||
Plugin.class.getResource(
|
||||
"/de/bluecolored/bluemap/" + minecraftVersion.getResource().getResourcePrefix() +
|
||||
"/resourceExtensions.zip")
|
||||
);
|
||||
FileUtils.copyURLToFile(resourceExtensionsUrl, resourceExtensionsFile.toFile(), 10000, 10000);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
"Failed to create resourceExtensions.zip!\n" +
|
||||
"Does BlueMap has sufficient write permissions?",
|
||||
ex);
|
||||
}
|
||||
|
||||
try {
|
||||
resourcePack = new ResourcePack();
|
||||
|
||||
List<Path> resourcePackRoots = new ArrayList<>();
|
||||
// load from resourcepack folder
|
||||
try (Stream<Path> resourcepackFiles = Files.list(resourcePackFolder)) {
|
||||
resourcepackFiles
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.forEach(resourcePackRoots::add);
|
||||
}
|
||||
|
||||
if (configs.getCoreConfig().isScanForModResources()) {
|
||||
|
||||
// load from mods folder
|
||||
Path modsFolder = serverInterface.getModsFolder().orElse(null);
|
||||
if (modsFolder != null && Files.isDirectory(modsFolder)) {
|
||||
try (Stream<Path> resourcepackFiles = Files.list(modsFolder)) {
|
||||
resourcepackFiles
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(file -> file.getFileName().toString().endsWith(".jar"))
|
||||
.forEach(resourcePackRoots::add);
|
||||
}
|
||||
}
|
||||
|
||||
// load from datapacks
|
||||
for (Path worldFolder : getWorldFolders()) {
|
||||
Path datapacksFolder = worldFolder.resolve("datapacks");
|
||||
if (!Files.isDirectory(datapacksFolder)) continue;
|
||||
|
||||
try (Stream<Path> resourcepackFiles = Files.list(worldFolder.resolve("datapacks"))) {
|
||||
resourcepackFiles.forEach(resourcePackRoots::add);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resourcePackRoots.add(resourceExtensionsFile);
|
||||
resourcePackRoots.add(defaultResourceFile);
|
||||
|
||||
resourcePack.loadResources(resourcePackRoots);
|
||||
ResourcePack resourcePack = new ResourcePack(minecraftVersion.getResourcePackVersion());
|
||||
resourcePack.loadResources(packRoots);
|
||||
this.resourcePack = resourcePack;
|
||||
} catch (IOException | RuntimeException e) {
|
||||
throw new ConfigurationException("Failed to parse resources!\n" +
|
||||
"Is one of your resource-packs corrupted?", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return resourcePack;
|
||||
return this.resourcePack;
|
||||
}
|
||||
|
||||
public Optional<ResourcePack> getResourcePackIfLoaded() {
|
||||
return Optional.ofNullable(this.resourcePack);
|
||||
}
|
||||
public synchronized DataPack loadDataPack(Path worldFolder) throws ConfigurationException, InterruptedException {
|
||||
MinecraftVersion minecraftVersion = getOrLoadMinecraftVersion();
|
||||
Path vanillaDataPack = minecraftVersion.getDataPack();
|
||||
|
||||
private Collection<Path> getWorldFolders() {
|
||||
Set<Path> folders = new HashSet<>();
|
||||
for (MapConfig mapConfig : configs.getMapConfigs().values()) {
|
||||
Path folder = mapConfig.getWorld();
|
||||
if (folder == null) continue;
|
||||
folder = folder.toAbsolutePath().normalize();
|
||||
if (Files.isDirectory(folder)) {
|
||||
folders.add(folder);
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
// also load world datapacks
|
||||
Iterable<Path> worldPacks = List.of();
|
||||
Path worldPacksFolder = worldFolder.resolve("datapacks");
|
||||
if (Files.isDirectory(worldPacksFolder)) {
|
||||
try (Stream<Path> worldPacksStream = Files.list(worldPacksFolder)) {
|
||||
worldPacks = worldPacksStream.toList();
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationException("Failed to access the worlds datapacks folder.", e);
|
||||
}
|
||||
}
|
||||
return folders;
|
||||
|
||||
Deque<Path> packRoots = getPackRoots(worldPacks);
|
||||
packRoots.addLast(vanillaDataPack);
|
||||
|
||||
try {
|
||||
DataPack datapack = new DataPack(minecraftVersion.getDataPackVersion());
|
||||
datapack.loadResources(packRoots);
|
||||
return datapack;
|
||||
} catch (IOException | RuntimeException e) {
|
||||
throw new ConfigurationException("Failed to parse resources!\n" +
|
||||
"Is one of your resource-packs corrupted?", e);
|
||||
}
|
||||
}
|
||||
|
||||
public BlueMapConfigProvider getConfigs() {
|
||||
return configs;
|
||||
private synchronized Deque<Path> getPackRoots(Path... additionalRoots) throws ConfigurationException, InterruptedException {
|
||||
return getPackRoots(List.of(additionalRoots));
|
||||
}
|
||||
|
||||
private synchronized Deque<Path> getPackRoots(Iterable<Path> additionalRoots) throws ConfigurationException, InterruptedException {
|
||||
@Nullable Path packsFolder = config.getPacksFolder();
|
||||
@Nullable Path modsFolder = config.getModsFolder();
|
||||
|
||||
try {
|
||||
FileHelper.createDirectories(packsFolder);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
"BlueMap failed to create this folder:\n" +
|
||||
packsFolder + "\n" +
|
||||
"Does BlueMap have sufficient permissions?",
|
||||
ex);
|
||||
}
|
||||
|
||||
Path resourceExtensionsFile = config.getCoreConfig().getData().resolve("resourceExtensions.zip");
|
||||
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(resourceExtensionsFile);
|
||||
FileHelper.createDirectories(resourceExtensionsFile.getParent());
|
||||
URL resourceExtensionsUrl = Objects.requireNonNull(
|
||||
Plugin.class.getResource("/de/bluecolored/bluemap/resourceExtensions.zip")
|
||||
);
|
||||
FileHelper.copy(resourceExtensionsUrl, resourceExtensionsFile);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
"Failed to create resourceExtensions.zip!\n" +
|
||||
"Does BlueMap has sufficient write permissions?",
|
||||
ex);
|
||||
}
|
||||
|
||||
Deque<Path> packRoots = new LinkedList<>();
|
||||
|
||||
// load from pack folder
|
||||
if (packsFolder != null && Files.isDirectory(packsFolder)) {
|
||||
try (Stream<Path> packFiles = Files.list(packsFolder)) {
|
||||
packFiles
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.forEach(packRoots::add);
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationException("Failed to access packs folder.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// add additional roots
|
||||
additionalRoots.forEach(packRoots::add);
|
||||
|
||||
// load from mods folder
|
||||
if (config.getCoreConfig().isScanForModResources() && modsFolder != null && Files.isDirectory(modsFolder)) {
|
||||
try (Stream<Path> packFiles = Files.list(modsFolder)) {
|
||||
packFiles
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(file -> file.getFileName().toString().endsWith(".jar"))
|
||||
.forEach(packRoots::add);
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationException("Failed to access packs folder.", e);
|
||||
}
|
||||
}
|
||||
|
||||
packRoots.add(resourceExtensionsFile);
|
||||
return packRoots;
|
||||
}
|
||||
|
||||
public synchronized MinecraftVersion getOrLoadMinecraftVersion() throws ConfigurationException {
|
||||
if (this.minecraftVersion == null) {
|
||||
try {
|
||||
this.minecraftVersion = MinecraftVersion.load(
|
||||
config.getMinecraftVersion(),
|
||||
config.getCoreConfig().getData(),
|
||||
config.getCoreConfig().isAcceptDownload()
|
||||
);
|
||||
} catch (IOException ex) {
|
||||
if (!config.getCoreConfig().isAcceptDownload()) {
|
||||
throw new MissingResourcesException();
|
||||
} else {
|
||||
throw new ConfigurationException("""
|
||||
BlueMap was not able to download some important resources!
|
||||
Make sure BlueMap is able to connect to mojang-servers (%s)."""
|
||||
.formatted(VersionManifest.DOMAIN), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.minecraftVersion;
|
||||
}
|
||||
|
||||
public BlueMapConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,28 +24,31 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.bluecolored.bluemap.common.config.WebappConfig;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class WebFilesManager {
|
||||
|
||||
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||
//.setPrettyPrinting() // enable pretty printing for easy editing
|
||||
.create();
|
||||
|
||||
private final Path webRoot;
|
||||
private Settings settings;
|
||||
|
||||
|
@ -60,7 +63,7 @@ public class WebFilesManager {
|
|||
|
||||
public void loadSettings() throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(getSettingsFile())) {
|
||||
this.settings = ResourcesGson.INSTANCE.fromJson(reader, Settings.class);
|
||||
this.settings = GSON.fromJson(reader, Settings.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,10 +71,7 @@ public class WebFilesManager {
|
|||
FileHelper.createDirectories(getSettingsFile().getParent());
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(getSettingsFile(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
ResourcesGson.addAdapter(new GsonBuilder())
|
||||
.setPrettyPrinting() // enable pretty printing for easy editing
|
||||
.create()
|
||||
.toJson(this.settings, writer);
|
||||
GSON.toJson(this.settings, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,39 +108,17 @@ public class WebFilesManager {
|
|||
}
|
||||
|
||||
public void updateFiles() throws IOException {
|
||||
URL fileResource = getClass().getResource("/de/bluecolored/bluemap/webapp.zip");
|
||||
File tempFile = File.createTempFile("bluemap_webroot_extraction", null);
|
||||
URL zippedWebapp = getClass().getResource("/de/bluecolored/bluemap/webapp.zip");
|
||||
if (zippedWebapp == null) throw new IOException("Failed to open bundled webapp.");
|
||||
|
||||
if (fileResource == null) throw new IOException("Failed to open bundled webapp.");
|
||||
// extract zip to webroot
|
||||
FileHelper.extractZipFile(zippedWebapp, webRoot, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
try {
|
||||
FileUtils.copyURLToFile(fileResource, tempFile, 10000, 10000);
|
||||
try (ZipFile zipFile = new ZipFile(tempFile)){
|
||||
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||
while(entries.hasMoreElements()) {
|
||||
ZipEntry zipEntry = entries.nextElement();
|
||||
if (zipEntry.isDirectory()) {
|
||||
File dir = webRoot.resolve(zipEntry.getName()).toFile();
|
||||
FileUtils.forceMkdir(dir);
|
||||
} else {
|
||||
File target = webRoot.resolve(zipEntry.getName()).toFile();
|
||||
FileUtils.forceMkdirParent(target);
|
||||
FileUtils.copyInputStreamToFile(zipFile.getInputStream(zipEntry), target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set version in index.html
|
||||
Path indexFile = webRoot.resolve("index.html");
|
||||
String indexContent = Files.readString(indexFile);
|
||||
indexContent = indexContent.replace("%version%", BlueMap.VERSION);
|
||||
Files.writeString(indexFile, indexContent);
|
||||
|
||||
} finally {
|
||||
if (!tempFile.delete()) {
|
||||
Logger.global.logWarning("Failed to delete file: " + tempFile);
|
||||
}
|
||||
}
|
||||
// set version in index.html
|
||||
Path indexFile = webRoot.resolve("index.html");
|
||||
String indexContent = Files.readString(indexFile);
|
||||
indexContent = indexContent.replace("%version%", BlueMap.VERSION);
|
||||
Files.writeString(indexFile, indexContent);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal", "unused", "MismatchedQueryAndUpdateOfCollection"})
|
||||
|
@ -197,8 +175,17 @@ public class WebFilesManager {
|
|||
}
|
||||
|
||||
public void addFrom(WebappConfig config) {
|
||||
this.scripts.addAll(config.getScripts());
|
||||
this.styles.addAll(config.getStyles());
|
||||
Set<String> scripts = config.getScripts();
|
||||
for (String script : scripts) {
|
||||
this.scripts.add(script);
|
||||
Logger.global.logDebug("Registering script from Webapp Config: " + script);
|
||||
}
|
||||
|
||||
Set<String> styles = config.getStyles();
|
||||
for (String style : styles) {
|
||||
this.styles.add(style);
|
||||
Logger.global.logDebug("Registering style from Webapp Config: " + style);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.addons;
|
||||
|
||||
import lombok.experimental.StandardException;
|
||||
|
||||
@StandardException
|
||||
public class AddonException extends Exception {}
|
|
@ -22,16 +22,15 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.storage.file;
|
||||
package de.bluecolored.bluemap.common.addons;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@Getter
|
||||
public class AddonInfo {
|
||||
public static final String ADDON_INFO_FILE = "bluemap.addon.json";
|
||||
|
||||
public interface FileStorageSettings {
|
||||
|
||||
Path getRoot();
|
||||
|
||||
Compression getCompression();
|
||||
private String id;
|
||||
private String entrypoint;
|
||||
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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.addons;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static de.bluecolored.bluemap.common.addons.AddonInfo.ADDON_INFO_FILE;
|
||||
|
||||
public final class Addons {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
private static final Map<String, LoadedAddon> LOADED_ADDONS = new ConcurrentHashMap<>();
|
||||
|
||||
private Addons() {
|
||||
throw new UnsupportedOperationException("Utility class");
|
||||
}
|
||||
|
||||
public static void tryLoadAddons(Path root) {
|
||||
tryLoadAddons(root, false);
|
||||
}
|
||||
|
||||
public static void tryLoadAddons(Path root, boolean expectOnlyAddons) {
|
||||
if (!Files.exists(root)) return;
|
||||
try (Stream<Path> files = Files.list(root)) {
|
||||
files
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(f -> f.getFileName().toString().endsWith(".jar"))
|
||||
.forEach(expectOnlyAddons ? Addons::tryLoadAddon : Addons::tryLoadJar);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to load addons from '%s'".formatted(root), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tryLoadAddon(Path addonJarFile) {
|
||||
try {
|
||||
AddonInfo addonInfo = loadAddonInfo(addonJarFile);
|
||||
if (addonInfo == null) throw new AddonException("No %s found in '%s'".formatted(ADDON_INFO_FILE, addonJarFile));
|
||||
|
||||
if (LOADED_ADDONS.containsKey(addonInfo.getId())) return;
|
||||
|
||||
loadAddon(addonJarFile, addonInfo);
|
||||
} catch (IOException | AddonException e) {
|
||||
Logger.global.logError("Failed to load addon '%s'".formatted(addonJarFile), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tryLoadJar(Path addonJarFile) {
|
||||
try {
|
||||
AddonInfo addonInfo = loadAddonInfo(addonJarFile);
|
||||
if (addonInfo == null) {
|
||||
Logger.global.logDebug("No %s found in '%s', skipping...".formatted(ADDON_INFO_FILE, addonJarFile));
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOADED_ADDONS.containsKey(addonInfo.getId())) return;
|
||||
|
||||
loadAddon(addonJarFile, addonInfo);
|
||||
} catch (IOException | AddonException e) {
|
||||
Logger.global.logError("Failed to load addon '%s'".formatted(addonJarFile), e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized static void loadAddon(Path jarFile, AddonInfo addonInfo) throws AddonException {
|
||||
Logger.global.logInfo("Loading BlueMap Addon: %s (%s)".formatted(addonInfo.getId(), jarFile));
|
||||
|
||||
if (LOADED_ADDONS.containsKey(addonInfo.getId()))
|
||||
throw new AddonException("Addon with id '%s' is already loaded".formatted(addonInfo.getId()));
|
||||
|
||||
try {
|
||||
ClassLoader addonClassLoader = BlueMap.class.getClassLoader();
|
||||
Class<?> entrypointClass;
|
||||
|
||||
// try to find entrypoint class and load jar with new classloader if needed
|
||||
try {
|
||||
entrypointClass = addonClassLoader.loadClass(addonInfo.getEntrypoint());
|
||||
} catch (ClassNotFoundException e) {
|
||||
addonClassLoader = new URLClassLoader(
|
||||
new URL[]{ jarFile.toUri().toURL() },
|
||||
BlueMap.class.getClassLoader()
|
||||
);
|
||||
entrypointClass = addonClassLoader.loadClass(addonInfo.getEntrypoint());
|
||||
}
|
||||
|
||||
// create addon instance
|
||||
Object instance = entrypointClass.getConstructor().newInstance();
|
||||
LoadedAddon addon = new LoadedAddon(
|
||||
addonInfo,
|
||||
addonClassLoader,
|
||||
instance
|
||||
);
|
||||
LOADED_ADDONS.put(addonInfo.getId(), addon);
|
||||
|
||||
// run addon
|
||||
if (instance instanceof Runnable runnable)
|
||||
runnable.run();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new AddonException("Failed to load addon '%s'".formatted(jarFile), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable AddonInfo loadAddonInfo(Path addonJarFile) throws IOException, AddonException {
|
||||
try (FileSystem fileSystem = FileSystems.newFileSystem(addonJarFile, (ClassLoader) null)) {
|
||||
for (Path root : fileSystem.getRootDirectories()) {
|
||||
Path addonInfoFile = root.resolve(ADDON_INFO_FILE);
|
||||
if (!Files.exists(addonInfoFile)) continue;
|
||||
|
||||
try (Reader reader = Files.newBufferedReader(addonInfoFile, StandardCharsets.UTF_8)) {
|
||||
AddonInfo addonInfo = GSON.fromJson(reader, AddonInfo.class);
|
||||
|
||||
if (addonInfo.getId() == null)
|
||||
throw new AddonException("'id' is missing");
|
||||
|
||||
if (addonInfo.getEntrypoint() == null)
|
||||
throw new AddonException("'entrypoint' is missing");
|
||||
|
||||
return addonInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.addons;
|
||||
|
||||
public record LoadedAddon (
|
||||
AddonInfo addonInfo,
|
||||
ClassLoader classLoader,
|
||||
Object instance
|
||||
) {}
|
|
@ -25,7 +25,8 @@
|
|||
package de.bluecolored.bluemap.common.api;
|
||||
|
||||
import de.bluecolored.bluemap.api.AssetStorage;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -34,39 +35,39 @@ import java.util.Optional;
|
|||
|
||||
public class AssetStorageImpl implements AssetStorage {
|
||||
|
||||
private static final String ASSET_PATH = "assets/";
|
||||
|
||||
private final Storage storage;
|
||||
private final MapStorage storage;
|
||||
private final String mapId;
|
||||
|
||||
public AssetStorageImpl(Storage storage, String mapId) {
|
||||
public AssetStorageImpl(MapStorage storage, String mapId) {
|
||||
this.storage = storage;
|
||||
this.mapId = mapId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream writeAsset(String name) throws IOException {
|
||||
return storage.writeMeta(mapId, ASSET_PATH + name);
|
||||
return storage.asset(name).write();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InputStream> readAsset(String name) throws IOException {
|
||||
return storage.readMeta(mapId, ASSET_PATH + name);
|
||||
CompressedInputStream in = storage.asset(name).read();
|
||||
if (in == null) return Optional.empty();
|
||||
return Optional.of(in.decompress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean assetExists(String name) throws IOException {
|
||||
return storage.readMetaInfo(mapId, ASSET_PATH + name).isPresent();
|
||||
return storage.asset(name).exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetUrl(String name) {
|
||||
return "maps/" + mapId + "/" + Storage.escapeMetaName(ASSET_PATH + name);
|
||||
return "maps/" + mapId + "/assets/" + MapStorage.escapeAssetName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAsset(String name) throws IOException {
|
||||
storage.deleteMeta(mapId, ASSET_PATH + name);
|
||||
storage.asset(name).delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,25 +29,44 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
|
|||
import de.bluecolored.bluemap.api.BlueMapAPI;
|
||||
import de.bluecolored.bluemap.api.BlueMapMap;
|
||||
import de.bluecolored.bluemap.api.BlueMapWorld;
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BlueMapAPIImpl extends BlueMapAPI {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final BlueMapService blueMapService;
|
||||
private final @Nullable Plugin plugin;
|
||||
|
||||
private final WebAppImpl webAppImpl;
|
||||
private final @Nullable RenderManagerImpl renderManagerImpl;
|
||||
private final @Nullable PluginImpl pluginImpl;
|
||||
|
||||
private final LoadingCache<Object, Optional<BlueMapWorld>> worldCache;
|
||||
private final LoadingCache<String, Optional<BlueMapMap>> mapCache;
|
||||
|
||||
public BlueMapAPIImpl(Plugin plugin) {
|
||||
this(plugin.getBlueMap(), plugin);
|
||||
}
|
||||
|
||||
public BlueMapAPIImpl(BlueMapService blueMapService, @Nullable Plugin plugin) {
|
||||
this.blueMapService = blueMapService;
|
||||
this.plugin = plugin;
|
||||
|
||||
this.renderManagerImpl = plugin != null ? new RenderManagerImpl(this, plugin) : null;
|
||||
this.webAppImpl = new WebAppImpl(blueMapService, plugin);
|
||||
this.pluginImpl = plugin != null ? new PluginImpl(plugin) : null;
|
||||
|
||||
this.worldCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.weakKeys()
|
||||
|
@ -58,47 +77,23 @@ public class BlueMapAPIImpl extends BlueMapAPI {
|
|||
.build(this::getMapUncached);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderManagerImpl getRenderManager() {
|
||||
return new RenderManagerImpl(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAppImpl getWebApp() {
|
||||
return new WebAppImpl(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public de.bluecolored.bluemap.api.plugin.Plugin getPlugin() {
|
||||
return new PluginImpl(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BlueMapMap> getMaps() {
|
||||
Map<String, BmMap> maps = plugin.getMaps();
|
||||
if (maps == null) return Collections.emptyList();
|
||||
|
||||
return maps.values().stream()
|
||||
.map(map -> {
|
||||
try {
|
||||
return new BlueMapMapImpl(plugin, map);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("[API] Failed to create BlueMapMap for map " + map.getId(), e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
Map<String, BmMap> maps = blueMapService.getMaps();
|
||||
return maps.keySet().stream()
|
||||
.map(this::getMap)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BlueMapWorld> getWorlds() {
|
||||
Map<String, World> worlds = plugin.getWorlds();
|
||||
if (worlds == null) return Collections.emptyList();
|
||||
|
||||
return worlds.values().stream()
|
||||
.map(world -> getWorld(world).orElse(null))
|
||||
.filter(Objects::nonNull)
|
||||
Map<String, World> worlds = blueMapService.getWorlds();
|
||||
return worlds.keySet().stream()
|
||||
.map(this::getWorld)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
|
@ -108,42 +103,25 @@ public class BlueMapAPIImpl extends BlueMapAPI {
|
|||
}
|
||||
|
||||
public Optional<BlueMapWorld> getWorldUncached(Object world) {
|
||||
var worlds = plugin.getWorlds();
|
||||
if (worlds == null) return Optional.empty();
|
||||
|
||||
if (world instanceof UUID) {
|
||||
var coreWorld = worlds.get(world.toString());
|
||||
if (coreWorld != null) world = coreWorld;
|
||||
}
|
||||
|
||||
if (world instanceof String) {
|
||||
var coreWorld = worlds.get(world);
|
||||
var coreWorld = blueMapService.getWorlds().get(world);
|
||||
if (coreWorld != null) world = coreWorld;
|
||||
}
|
||||
|
||||
if (world instanceof World) {
|
||||
var coreWorld = (World) world;
|
||||
try {
|
||||
return Optional.of(new BlueMapWorldImpl(plugin, coreWorld));
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("[API] Failed to create BlueMapWorld for world " + coreWorld.getSaveFolder(), e);
|
||||
}
|
||||
return Optional.empty();
|
||||
if (world instanceof World coreWorld) {
|
||||
return Optional.of(new BlueMapWorldImpl(coreWorld, blueMapService, plugin));
|
||||
}
|
||||
|
||||
var serverWorld = plugin.getServerInterface().getWorld(world).orElse(null);
|
||||
if (plugin == null) return Optional.empty();
|
||||
|
||||
ServerWorld serverWorld = plugin.getServerInterface().getServerWorld(world).orElse(null);
|
||||
if (serverWorld == null) return Optional.empty();
|
||||
|
||||
try {
|
||||
String id = plugin.getBlueMap().getWorldId(serverWorld.getSaveFolder());
|
||||
var coreWorld = worlds.get(id);
|
||||
if (coreWorld == null) return Optional.empty();
|
||||
World coreWorld = plugin.getWorld(serverWorld);
|
||||
if (coreWorld == null) return Optional.empty();
|
||||
|
||||
return Optional.of(new BlueMapWorldImpl(plugin, coreWorld));
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("[API] Failed to create BlueMapWorld for world " + serverWorld.getSaveFolder(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(new BlueMapWorldImpl(coreWorld, blueMapService, plugin));
|
||||
|
||||
}
|
||||
|
||||
|
@ -153,8 +131,7 @@ public class BlueMapAPIImpl extends BlueMapAPI {
|
|||
}
|
||||
|
||||
public Optional<BlueMapMap> getMapUncached(String id) {
|
||||
var maps = plugin.getMaps();
|
||||
if (maps == null) return Optional.empty();
|
||||
var maps = blueMapService.getMaps();
|
||||
|
||||
var map = maps.get(id);
|
||||
if (map == null) return Optional.empty();
|
||||
|
@ -162,7 +139,7 @@ public class BlueMapAPIImpl extends BlueMapAPI {
|
|||
var world = getWorld(map.getWorld()).orElse(null);
|
||||
if (world == null) return Optional.empty();
|
||||
|
||||
return Optional.of(new BlueMapMapImpl(plugin, map, (BlueMapWorldImpl) world));
|
||||
return Optional.of(new BlueMapMapImpl(map, (BlueMapWorldImpl) world, plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -170,10 +147,27 @@ public class BlueMapAPIImpl extends BlueMapAPI {
|
|||
return BlueMap.VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAppImpl getWebApp() {
|
||||
return webAppImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderManagerImpl getRenderManager() {
|
||||
if (renderManagerImpl == null) throw new UnsupportedOperationException("RenderManager API is not supported on this platform");
|
||||
return renderManagerImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public de.bluecolored.bluemap.api.plugin.Plugin getPlugin() {
|
||||
if (pluginImpl == null) throw new UnsupportedOperationException("Plugin API is not supported on this platform");
|
||||
return pluginImpl;
|
||||
}
|
||||
|
||||
public void register() {
|
||||
try {
|
||||
BlueMapAPI.registerInstance(this);
|
||||
} catch (ExecutionException ex) {
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logError("BlueMapAPI: A BlueMapAPI listener threw an exception (onEnable)!", ex.getCause());
|
||||
}
|
||||
}
|
||||
|
@ -181,9 +175,31 @@ public class BlueMapAPIImpl extends BlueMapAPI {
|
|||
public void unregister() {
|
||||
try {
|
||||
BlueMapAPI.unregisterInstance(this);
|
||||
} catch (ExecutionException ex) {
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logError("BlueMapAPI: A BlueMapAPI listener threw an exception (onDisable)!", ex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy-access method for addons depending on BlueMapCommon:<br>
|
||||
* <blockquote><pre>
|
||||
* BlueMapService bluemap = ((BlueMapAPIImpl) blueMapAPI).blueMapService();
|
||||
* </pre></blockquote>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public BlueMapService blueMapService() {
|
||||
return blueMapService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy-access method for addons depending on BlueMapCommon:<br>
|
||||
* <blockquote><pre>
|
||||
* Plugin plugin = ((BlueMapAPIImpl) blueMapAPI).plugin();
|
||||
* </pre></blockquote>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public @Nullable Plugin plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,16 +25,16 @@
|
|||
package de.bluecolored.bluemap.common.api;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.api.AssetStorage;
|
||||
import de.bluecolored.bluemap.api.BlueMapMap;
|
||||
import de.bluecolored.bluemap.api.BlueMapWorld;
|
||||
import de.bluecolored.bluemap.api.AssetStorage;
|
||||
import de.bluecolored.bluemap.api.markers.MarkerSet;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
|
||||
import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -42,29 +42,21 @@ import java.util.function.Predicate;
|
|||
|
||||
public class BlueMapMapImpl implements BlueMapMap {
|
||||
|
||||
private final WeakReference<Plugin> plugin;
|
||||
private final String mapId;
|
||||
private final WeakReference<BmMap> map;
|
||||
private final BlueMapWorldImpl world;
|
||||
private final WeakReference<Plugin> plugin;
|
||||
|
||||
public BlueMapMapImpl(Plugin plugin, BmMap map) throws IOException {
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
this.map = new WeakReference<>(map);
|
||||
this.world = new BlueMapWorldImpl(plugin, map.getWorld());
|
||||
}
|
||||
|
||||
public BlueMapMapImpl(Plugin plugin, BmMap map, BlueMapWorldImpl world) {
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
public BlueMapMapImpl(BmMap map, BlueMapWorldImpl world, @Nullable Plugin plugin) {
|
||||
this.mapId = map.getId();
|
||||
this.map = new WeakReference<>(map);
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
public BmMap getBmMap() {
|
||||
return unpack(map);
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return unpack(map).getId();
|
||||
return mapId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -119,7 +111,9 @@ public class BlueMapMapImpl implements BlueMapMap {
|
|||
}
|
||||
|
||||
private synchronized void unfreeze() {
|
||||
Plugin plugin = unpack(this.plugin);
|
||||
Plugin plugin = this.plugin.get();
|
||||
if (plugin == null) return; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
BmMap map = unpack(this.map);
|
||||
plugin.startWatchingMap(map);
|
||||
plugin.getPluginState().getMapState(map).setUpdateEnabled(true);
|
||||
|
@ -127,7 +121,9 @@ public class BlueMapMapImpl implements BlueMapMap {
|
|||
}
|
||||
|
||||
private synchronized void freeze() {
|
||||
Plugin plugin = unpack(this.plugin);
|
||||
Plugin plugin = this.plugin.get();
|
||||
if (plugin == null) return; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
BmMap map = unpack(this.map);
|
||||
plugin.stopWatchingMap(map);
|
||||
plugin.getPluginState().getMapState(map).setUpdateEnabled(false);
|
||||
|
@ -144,11 +140,39 @@ public class BlueMapMapImpl implements BlueMapMap {
|
|||
|
||||
@Override
|
||||
public boolean isFrozen() {
|
||||
return !unpack(plugin).getPluginState().getMapState(unpack(map)).isUpdateEnabled();
|
||||
Plugin plugin = this.plugin.get();
|
||||
if (plugin == null) return false; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
return !plugin.getPluginState().getMapState(unpack(map)).isUpdateEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
BlueMapMapImpl that = (BlueMapMapImpl) o;
|
||||
|
||||
return mapId.equals(that.mapId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mapId.hashCode();
|
||||
}
|
||||
|
||||
private <T> T unpack(WeakReference<T> ref) {
|
||||
return Objects.requireNonNull(ref.get(), "Reference lost to delegate object. Most likely BlueMap got reloaded and this instance is no longer valid.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy-access method for addons depending on BlueMapCore:<br>
|
||||
* <blockquote><pre>
|
||||
* BmMap map = ((BlueMapMapImpl) blueMapMap).map();
|
||||
* </pre></blockquote>
|
||||
*/
|
||||
public BmMap map() {
|
||||
return unpack(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,10 +26,12 @@ package de.bluecolored.bluemap.common.api;
|
|||
|
||||
import de.bluecolored.bluemap.api.BlueMapMap;
|
||||
import de.bluecolored.bluemap.api.BlueMapWorld;
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
@ -38,18 +40,16 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class BlueMapWorldImpl implements BlueMapWorld {
|
||||
|
||||
private final WeakReference<Plugin> plugin;
|
||||
private final String id;
|
||||
private final WeakReference<World> world;
|
||||
private final WeakReference<BlueMapService> blueMapService;
|
||||
private final WeakReference<Plugin> plugin;
|
||||
|
||||
public BlueMapWorldImpl(Plugin plugin, World world) throws IOException {
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
this.id = plugin.getBlueMap().getWorldId(world.getSaveFolder());
|
||||
public BlueMapWorldImpl(World world, BlueMapService blueMapService, @Nullable Plugin plugin) {
|
||||
this.id = world.getId();
|
||||
this.world = new WeakReference<>(world);
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return unpack(world);
|
||||
this.blueMapService = new WeakReference<>(blueMapService);
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -58,20 +58,52 @@ public class BlueMapWorldImpl implements BlueMapWorld {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Path getSaveFolder() {
|
||||
return unpack(world).getSaveFolder();
|
||||
World world = unpack(this.world);
|
||||
if (world instanceof MCAWorld) {
|
||||
return ((MCAWorld) world).getDimensionFolder();
|
||||
} else {
|
||||
throw new UnsupportedOperationException("This world-type has no save-folder.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BlueMapMap> getMaps() {
|
||||
return unpack(plugin).getMaps().values().stream()
|
||||
.filter(map -> map.getWorld().equals(unpack(world)))
|
||||
.map(map -> new BlueMapMapImpl(unpack(plugin), map, this))
|
||||
World world = unpack(this.world);
|
||||
return unpack(blueMapService).getMaps().values().stream()
|
||||
.filter(map -> map.getWorld().equals(world))
|
||||
.map(map -> new BlueMapMapImpl(map, this, plugin.get()))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
BlueMapWorldImpl that = (BlueMapWorldImpl) o;
|
||||
|
||||
return id.equals(that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
private <T> T unpack(WeakReference<T> ref) {
|
||||
return Objects.requireNonNull(ref.get(), "Reference lost to delegate object. Most likely BlueMap got reloaded and this instance is no longer valid.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy-access method for addons depending on BlueMapCore:<br>
|
||||
* <blockquote><pre>
|
||||
* World world = ((BlueMapWorldImpl) blueMapWorld).world();
|
||||
* </pre></blockquote>
|
||||
*/
|
||||
public World world() {
|
||||
return unpack(world);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import de.bluecolored.bluemap.common.plugin.Plugin;
|
|||
import de.bluecolored.bluemap.common.rendermanager.MapPurgeTask;
|
||||
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
public class RenderManagerImpl implements RenderManager {
|
||||
|
@ -49,19 +48,19 @@ public class RenderManagerImpl implements RenderManager {
|
|||
@Override
|
||||
public boolean scheduleMapUpdateTask(BlueMapMap map, boolean force) {
|
||||
BlueMapMapImpl cmap = castMap(map);
|
||||
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getBmMap(), force));
|
||||
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.map(), s -> force));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scheduleMapUpdateTask(BlueMapMap map, Collection<Vector2i> regions, boolean force) {
|
||||
BlueMapMapImpl cmap = castMap(map);
|
||||
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getBmMap(), regions, force));
|
||||
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.map(), regions, s -> force));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scheduleMapPurgeTask(BlueMapMap map) throws IOException {
|
||||
public boolean scheduleMapPurgeTask(BlueMapMap map) {
|
||||
BlueMapMapImpl cmap = castMap(map);
|
||||
return renderManager.scheduleRenderTask(new MapPurgeTask(cmap.getBmMap()));
|
||||
return renderManager.scheduleRenderTask(new MapPurgeTask(cmap.map()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -82,7 +81,7 @@ public class RenderManagerImpl implements RenderManager {
|
|||
@Override
|
||||
public void start() {
|
||||
if (!isRunning()){
|
||||
renderManager.start(plugin.getConfigs().getCoreConfig().getRenderThreadCount());
|
||||
renderManager.start(plugin.getBlueMap().getConfig().getCoreConfig().getRenderThreadCount());
|
||||
}
|
||||
plugin.getPluginState().setRenderThreadsEnabled(true);
|
||||
}
|
||||
|
|
|
@ -25,35 +25,47 @@
|
|||
package de.bluecolored.bluemap.common.api;
|
||||
|
||||
import de.bluecolored.bluemap.api.WebApp;
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class WebAppImpl implements WebApp {
|
||||
private static final Path IMAGE_ROOT_PATH = Path.of("data", "images");
|
||||
|
||||
private final Plugin plugin;
|
||||
private final BlueMapService blueMapService;
|
||||
private final @Nullable Plugin plugin;
|
||||
|
||||
private final Timer timer = new Timer("BlueMap-WebbAppImpl-Timer", true);
|
||||
private @Nullable TimerTask scheduledWebAppSettingsUpdate;
|
||||
|
||||
public WebAppImpl(BlueMapService blueMapService, @Nullable Plugin plugin) {
|
||||
this.blueMapService = blueMapService;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public WebAppImpl(Plugin plugin) {
|
||||
this.blueMapService = plugin.getBlueMap();
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getWebRoot() {
|
||||
return plugin.getConfigs().getWebappConfig().getWebroot();
|
||||
return blueMapService.getConfig().getWebappConfig().getWebroot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerVisibility(UUID player, boolean visible) {
|
||||
if (plugin == null) return; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
if (visible) {
|
||||
plugin.getPluginState().removeHiddenPlayer(player);
|
||||
} else {
|
||||
|
@ -63,29 +75,61 @@ public class WebAppImpl implements WebApp {
|
|||
|
||||
@Override
|
||||
public boolean getPlayerVisibility(UUID player) {
|
||||
if (plugin == null) return false; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
return !plugin.getPluginState().isPlayerHidden(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerScript(String url) {
|
||||
plugin.getBlueMap().getWebFilesManager().getScripts().add(url);
|
||||
public synchronized void registerScript(String url) {
|
||||
Logger.global.logDebug("Registering script from API: " + url);
|
||||
blueMapService.getWebFilesManager().getScripts().add(url);
|
||||
scheduleUpdateWebAppSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStyle(String url) {
|
||||
plugin.getBlueMap().getWebFilesManager().getStyles().add(url);
|
||||
public synchronized void registerStyle(String url) {
|
||||
Logger.global.logDebug("Registering style from API: " + url);
|
||||
blueMapService.getWebFilesManager().getStyles().add(url);
|
||||
scheduleUpdateWebAppSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save webapp-settings after a short delay, if no other save is already scheduled.
|
||||
* (to bulk-save changes in case there is a lot of scripts being registered at once)
|
||||
*/
|
||||
private synchronized void scheduleUpdateWebAppSettings() {
|
||||
if (!blueMapService.getConfig().getWebappConfig().isEnabled()) return;
|
||||
if (scheduledWebAppSettingsUpdate != null) return;
|
||||
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (WebAppImpl.this) {
|
||||
try {
|
||||
if (blueMapService.getConfig().getWebappConfig().isEnabled())
|
||||
blueMapService.getWebFilesManager().saveSettings();
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to update webapp settings", ex);
|
||||
} finally {
|
||||
scheduledWebAppSettingsUpdate = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public String createImage(BufferedImage image, String path) throws IOException {
|
||||
path = path.replaceAll("[^a-zA-Z0-9_.\\-/]", "_");
|
||||
|
||||
Path webRoot = getWebRoot().toAbsolutePath();
|
||||
String separator = webRoot.getFileSystem().getSeparator();
|
||||
|
||||
Path imageRootFolder = webRoot.resolve(IMAGE_ROOT_PATH);
|
||||
Path imagePath = imageRootFolder.resolve(Path.of(path.replace("/", separator) + ".png")).toAbsolutePath();
|
||||
Path imageRootFolder = webRoot.resolve("data").resolve("images");
|
||||
Path imagePath = imageRootFolder.resolve(path.replace("/", separator) + ".png").toAbsolutePath();
|
||||
|
||||
FileHelper.createDirectories(imagePath.getParent());
|
||||
Files.deleteIfExists(imagePath);
|
||||
|
@ -98,11 +142,13 @@ public class WebAppImpl implements WebApp {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public Map<String, String> availableImages() throws IOException {
|
||||
Path webRoot = getWebRoot().toAbsolutePath();
|
||||
String separator = webRoot.getFileSystem().getSeparator();
|
||||
|
||||
Path imageRootPath = webRoot.resolve("data").resolve(IMAGE_ROOT_PATH).toAbsolutePath();
|
||||
Path imageRootPath = webRoot.resolve("data").resolve("images").toAbsolutePath();
|
||||
|
||||
Map<String, String> availableImagesMap = new HashMap<>();
|
||||
|
||||
|
|
|
@ -24,28 +24,40 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.common.BlueMapConfigProvider;
|
||||
import de.bluecolored.bluemap.common.BlueMapConfiguration;
|
||||
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import de.bluecolored.bluemap.core.util.Tristate;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@DebugDump
|
||||
public class BlueMapConfigs implements BlueMapConfigProvider {
|
||||
@Getter
|
||||
public class BlueMapConfigManager implements BlueMapConfiguration {
|
||||
|
||||
public static final String CORE_CONFIG_NAME = "core";
|
||||
public static final String WEBSERVER_CONFIG_NAME = "webserver";
|
||||
public static final String WEBAPP_CONFIG_NAME = "webapp";
|
||||
public static final String PLUGIN_CONFIG_NAME = "plugin";
|
||||
public static final String MAPS_CONFIG_FOLDER_NAME = "maps";
|
||||
public static final String STORAGES_CONFIG_FOLDER_NAME = "storages";
|
||||
|
||||
public static final String MAP_STORAGE_CONFIG_NAME = MAPS_CONFIG_FOLDER_NAME + "/map";
|
||||
|
||||
public static final String FILE_STORAGE_CONFIG_NAME = STORAGES_CONFIG_FOLDER_NAME + "/file";
|
||||
public static final String SQL_STORAGE_CONFIG_NAME = STORAGES_CONFIG_FOLDER_NAME + "/sql";
|
||||
|
||||
private final ServerInterface serverInterface;
|
||||
private final ConfigManager configManager;
|
||||
|
||||
private final CoreConfig coreConfig;
|
||||
|
@ -54,85 +66,59 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
private final PluginConfig pluginConfig;
|
||||
private final Map<String, MapConfig> mapConfigs;
|
||||
private final Map<String, StorageConfig> storageConfigs;
|
||||
private final Path packsFolder;
|
||||
private final @Nullable String minecraftVersion;
|
||||
private final @Nullable Path modsFolder;
|
||||
|
||||
public BlueMapConfigs(ServerInterface serverInterface) throws ConfigurationException {
|
||||
this(serverInterface, Path.of("bluemap"), Path.of("bluemap", "web"), true);
|
||||
}
|
||||
@Builder
|
||||
private BlueMapConfigManager(
|
||||
@NonNull Path configRoot,
|
||||
@Nullable String minecraftVersion,
|
||||
@Nullable Path defaultDataFolder,
|
||||
@Nullable Path defaultWebroot,
|
||||
@Nullable Collection<ServerWorld> autoConfigWorlds,
|
||||
@Nullable Boolean usePluginConfig,
|
||||
@Nullable Boolean useMetricsConfig,
|
||||
@Nullable Path packsFolder,
|
||||
@Nullable Path modsFolder
|
||||
) throws ConfigurationException {
|
||||
// set defaults
|
||||
if (defaultDataFolder == null) defaultDataFolder = Path.of("bluemap");
|
||||
if (defaultWebroot == null) defaultWebroot = Path.of("bluemap", "web");
|
||||
if (autoConfigWorlds == null) autoConfigWorlds = Collections.emptyList();
|
||||
if (usePluginConfig == null) usePluginConfig = true;
|
||||
if (useMetricsConfig == null) useMetricsConfig = true;
|
||||
if (packsFolder == null) packsFolder = configRoot.resolve("packs");
|
||||
|
||||
public BlueMapConfigs(ServerInterface serverInterface, Path defaultDataFolder, Path defaultWebroot, boolean usePluginConf) throws ConfigurationException {
|
||||
this.serverInterface = serverInterface;
|
||||
this.configManager = new ConfigManager(serverInterface.getConfigFolder());
|
||||
|
||||
this.coreConfig = loadCoreConfig(defaultDataFolder);
|
||||
// load
|
||||
this.configManager = new ConfigManager(configRoot);
|
||||
this.coreConfig = loadCoreConfig(defaultDataFolder, useMetricsConfig);
|
||||
this.webappConfig = loadWebappConfig(defaultWebroot);
|
||||
this.webserverConfig = loadWebserverConfig(webappConfig.getWebroot(), coreConfig.getData());
|
||||
this.pluginConfig = usePluginConf ? loadPluginConfig() : new PluginConfig();
|
||||
this.pluginConfig = usePluginConfig ? loadPluginConfig() : new PluginConfig();
|
||||
this.storageConfigs = Collections.unmodifiableMap(loadStorageConfigs(webappConfig.getWebroot()));
|
||||
this.mapConfigs = Collections.unmodifiableMap(loadMapConfigs());
|
||||
this.mapConfigs = Collections.unmodifiableMap(loadMapConfigs(autoConfigWorlds));
|
||||
this.packsFolder = packsFolder;
|
||||
this.minecraftVersion = minecraftVersion;
|
||||
this.modsFolder = modsFolder;
|
||||
}
|
||||
|
||||
public ConfigManager getConfigManager() {
|
||||
return configManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoreConfig getCoreConfig() {
|
||||
return coreConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebappConfig getWebappConfig() {
|
||||
return webappConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebserverConfig getWebserverConfig() {
|
||||
return webserverConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginConfig getPluginConfig() {
|
||||
return pluginConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, MapConfig> getMapConfigs() {
|
||||
return mapConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, StorageConfig> getStorageConfigs() {
|
||||
return storageConfigs;
|
||||
}
|
||||
|
||||
private synchronized CoreConfig loadCoreConfig(Path defaultDataFolder) throws ConfigurationException {
|
||||
Path configFileRaw = Path.of("core");
|
||||
Path configFile = configManager.findConfigPath(configFileRaw);
|
||||
private CoreConfig loadCoreConfig(Path defaultDataFolder, boolean useMetricsConfig) throws ConfigurationException {
|
||||
Path configFile = configManager.resolveConfigFile(CORE_CONFIG_NAME);
|
||||
Path configFolder = configFile.getParent();
|
||||
|
||||
if (!Files.exists(configFile)) {
|
||||
|
||||
// determine render-thread preset (very pessimistic, rather let people increase it themselves)
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
int availableCores = runtime.availableProcessors();
|
||||
long availableMemoryMiB = runtime.maxMemory() / 1024L / 1024L;
|
||||
int presetRenderThreadCount = 1;
|
||||
if (availableCores >= 6 && availableMemoryMiB >= 4096)
|
||||
presetRenderThreadCount = 2;
|
||||
if (availableCores >= 10 && availableMemoryMiB >= 8192)
|
||||
presetRenderThreadCount = 3;
|
||||
|
||||
try {
|
||||
FileHelper.createDirectories(configFolder);
|
||||
Files.writeString(
|
||||
configFolder.resolve("core.conf"),
|
||||
configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/core.conf")
|
||||
.setConditional("metrics", serverInterface.isMetricsEnabled() == Tristate.UNDEFINED)
|
||||
configFile,
|
||||
configManager.loadConfigTemplate(CORE_CONFIG_NAME)
|
||||
.setConditional("metrics", useMetricsConfig)
|
||||
.setVariable("timestamp", LocalDateTime.now().withNano(0).toString())
|
||||
.setVariable("version", BlueMap.VERSION)
|
||||
.setVariable("data", formatPath(defaultDataFolder))
|
||||
.setVariable("implementation", "bukkit")
|
||||
.setVariable("render-thread-count", Integer.toString(presetRenderThreadCount))
|
||||
.setVariable("render-thread-count", Integer.toString(suggestRenderThreadCount()))
|
||||
.setVariable("logfile", formatPath(defaultDataFolder.resolve("logs").resolve("debug.log")))
|
||||
.setVariable("logfile-with-time", formatPath(defaultDataFolder.resolve("logs").resolve("debug_%1$tF_%1$tT.log")))
|
||||
.build(),
|
||||
|
@ -143,20 +129,34 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return configManager.loadConfig(configFileRaw, CoreConfig.class);
|
||||
return configManager.loadConfig(CORE_CONFIG_NAME, CoreConfig.class);
|
||||
}
|
||||
|
||||
private synchronized WebserverConfig loadWebserverConfig(Path defaultWebroot, Path dataRoot) throws ConfigurationException {
|
||||
Path configFileRaw = Path.of("webserver");
|
||||
Path configFile = configManager.findConfigPath(configFileRaw);
|
||||
/**
|
||||
* determine render-thread preset (very pessimistic, rather let people increase it themselves)
|
||||
*/
|
||||
private int suggestRenderThreadCount() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
int availableCores = runtime.availableProcessors();
|
||||
long availableMemoryMiB = runtime.maxMemory() / 1024L / 1024L;
|
||||
int presetRenderThreadCount = 1;
|
||||
if (availableCores >= 6 && availableMemoryMiB >= 4096)
|
||||
presetRenderThreadCount = 2;
|
||||
if (availableCores >= 10 && availableMemoryMiB >= 8192)
|
||||
presetRenderThreadCount = 3;
|
||||
return presetRenderThreadCount;
|
||||
}
|
||||
|
||||
private WebserverConfig loadWebserverConfig(Path defaultWebroot, Path dataRoot) throws ConfigurationException {
|
||||
Path configFile = configManager.resolveConfigFile(WEBSERVER_CONFIG_NAME);
|
||||
Path configFolder = configFile.getParent();
|
||||
|
||||
if (!Files.exists(configFile)) {
|
||||
try {
|
||||
FileHelper.createDirectories(configFolder);
|
||||
Files.writeString(
|
||||
configFolder.resolve("webserver.conf"),
|
||||
configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/webserver.conf")
|
||||
configFile,
|
||||
configManager.loadConfigTemplate(WEBSERVER_CONFIG_NAME)
|
||||
.setVariable("webroot", formatPath(defaultWebroot))
|
||||
.setVariable("logfile", formatPath(dataRoot.resolve("logs").resolve("webserver.log")))
|
||||
.setVariable("logfile-with-time", formatPath(dataRoot.resolve("logs").resolve("webserver_%1$tF_%1$tT.log")))
|
||||
|
@ -168,20 +168,19 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return configManager.loadConfig(configFileRaw, WebserverConfig.class);
|
||||
return configManager.loadConfig(WEBSERVER_CONFIG_NAME, WebserverConfig.class);
|
||||
}
|
||||
|
||||
private synchronized WebappConfig loadWebappConfig(Path defaultWebroot) throws ConfigurationException {
|
||||
Path configFileRaw = Path.of("webapp");
|
||||
Path configFile = configManager.findConfigPath(configFileRaw);
|
||||
private WebappConfig loadWebappConfig(Path defaultWebroot) throws ConfigurationException {
|
||||
Path configFile = configManager.resolveConfigFile(WEBAPP_CONFIG_NAME);
|
||||
Path configFolder = configFile.getParent();
|
||||
|
||||
if (!Files.exists(configFile)) {
|
||||
try {
|
||||
FileHelper.createDirectories(configFolder);
|
||||
Files.writeString(
|
||||
configFolder.resolve("webapp.conf"),
|
||||
configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/webapp.conf")
|
||||
configFile,
|
||||
configManager.loadConfigTemplate(WEBAPP_CONFIG_NAME)
|
||||
.setVariable("webroot", formatPath(defaultWebroot))
|
||||
.build(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||
|
@ -191,20 +190,19 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return configManager.loadConfig(configFileRaw, WebappConfig.class);
|
||||
return configManager.loadConfig(WEBAPP_CONFIG_NAME, WebappConfig.class);
|
||||
}
|
||||
|
||||
private synchronized PluginConfig loadPluginConfig() throws ConfigurationException {
|
||||
Path configFileRaw = Path.of("plugin");
|
||||
Path configFile = configManager.findConfigPath(configFileRaw);
|
||||
private PluginConfig loadPluginConfig() throws ConfigurationException {
|
||||
Path configFile = configManager.resolveConfigFile(PLUGIN_CONFIG_NAME);
|
||||
Path configFolder = configFile.getParent();
|
||||
|
||||
if (!Files.exists(configFile)) {
|
||||
try {
|
||||
FileHelper.createDirectories(configFolder);
|
||||
Files.writeString(
|
||||
configFolder.resolve("plugin.conf"),
|
||||
configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/plugin.conf")
|
||||
configFile,
|
||||
configManager.loadConfigTemplate(PLUGIN_CONFIG_NAME)
|
||||
.build(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||
);
|
||||
|
@ -213,54 +211,73 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return configManager.loadConfig(configFileRaw, PluginConfig.class);
|
||||
return configManager.loadConfig(PLUGIN_CONFIG_NAME, PluginConfig.class);
|
||||
}
|
||||
|
||||
private synchronized Map<String, MapConfig> loadMapConfigs() throws ConfigurationException {
|
||||
private Map<String, MapConfig> loadMapConfigs(Collection<ServerWorld> autoConfigWorlds) throws ConfigurationException {
|
||||
Map<String, MapConfig> mapConfigs = new HashMap<>();
|
||||
|
||||
Path mapFolder = Paths.get("maps");
|
||||
Path mapConfigFolder = configManager.getConfigRoot().resolve(mapFolder);
|
||||
Path mapConfigFolder = configManager.getConfigRoot().resolve(MAPS_CONFIG_FOLDER_NAME);
|
||||
|
||||
if (!Files.exists(mapConfigFolder)){
|
||||
try {
|
||||
FileHelper.createDirectories(mapConfigFolder);
|
||||
var worlds = serverInterface.getLoadedWorlds();
|
||||
if (worlds.isEmpty()) {
|
||||
if (autoConfigWorlds.isEmpty()) {
|
||||
Path worldFolder = Path.of("world");
|
||||
Files.writeString(
|
||||
mapConfigFolder.resolve("overworld.conf"),
|
||||
createOverworldMapTemplate("Overworld", Path.of("world"), 0).build(),
|
||||
configManager.resolveConfigFile(MAPS_CONFIG_FOLDER_NAME + "/overworld"),
|
||||
createOverworldMapTemplate("Overworld", worldFolder,
|
||||
DataPack.DIMENSION_OVERWORLD, 0).build(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||
);
|
||||
Files.writeString(
|
||||
mapConfigFolder.resolve("nether.conf"),
|
||||
createNetherMapTemplate("Nether", Path.of("world", "DIM-1"), 0).build(),
|
||||
configManager.resolveConfigFile(MAPS_CONFIG_FOLDER_NAME + "/nether"),
|
||||
createNetherMapTemplate("Nether", worldFolder,
|
||||
DataPack.DIMENSION_THE_NETHER, 0).build(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||
);
|
||||
Files.writeString(
|
||||
mapConfigFolder.resolve("end.conf"),
|
||||
createEndMapTemplate("End", Path.of("world", "DIM1"), 0).build(),
|
||||
configManager.resolveConfigFile(MAPS_CONFIG_FOLDER_NAME + "/end"),
|
||||
createEndMapTemplate("End", worldFolder,
|
||||
DataPack.DIMENSION_THE_END, 0).build(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||
);
|
||||
} else {
|
||||
for (var world : worlds) {
|
||||
String name = world.getName().orElse(world.getDimension().getName());
|
||||
Path worldFolder = world.getSaveFolder();
|
||||
// make sure overworld-dimensions come first, so they are the ones where the
|
||||
// dimension-key is omitted in the generated map-id
|
||||
List<ServerWorld> overworldFirstAutoConfigWorlds = new ArrayList<>(autoConfigWorlds.size());
|
||||
overworldFirstAutoConfigWorlds.addAll(autoConfigWorlds);
|
||||
overworldFirstAutoConfigWorlds.sort(Comparator.comparingInt(w ->
|
||||
DataPack.DIMENSION_OVERWORLD.equals(w.getDimension()) ? 0 : 1
|
||||
));
|
||||
|
||||
Path configFile = mapConfigFolder.resolve(sanitiseMapId(name.toLowerCase(Locale.ROOT)) + ".conf");
|
||||
Set<String> mapIds = new HashSet<>();
|
||||
for (var world : overworldFirstAutoConfigWorlds) {
|
||||
Path worldFolder = world.getWorldFolder().normalize();
|
||||
Key dimension = world.getDimension();
|
||||
|
||||
String dimensionName = dimension.getNamespace().equals("minecraft") ?
|
||||
dimension.getValue() : dimension.getFormatted();
|
||||
|
||||
// find unique map id
|
||||
String id = sanitiseMapId(worldFolder.getFileName().toString()).toLowerCase(Locale.ROOT);
|
||||
if (mapIds.contains(id))
|
||||
id = sanitiseMapId(worldFolder.getFileName() + "_" + dimensionName).toLowerCase(Locale.ROOT);
|
||||
int i = 1;
|
||||
while (Files.exists(configFile)) {
|
||||
configFile = mapConfigFolder.resolve(sanitiseMapId(name.toLowerCase(Locale.ROOT)) + '_' + (++i) + ".conf");
|
||||
}
|
||||
String uniqueId = id;
|
||||
while (mapIds.contains(uniqueId))
|
||||
uniqueId = id + "_" + (++i);
|
||||
mapIds.add(uniqueId);
|
||||
|
||||
if (i > 1) name = name + " " + i;
|
||||
Path configFile = configManager.resolveConfigFile(MAPS_CONFIG_FOLDER_NAME + "/" + uniqueId);
|
||||
String name = worldFolder.getFileName() + " (" + dimensionName + ")";
|
||||
if (i > 1) name = name + " (" + i + ")";
|
||||
|
||||
ConfigTemplate template;
|
||||
switch (world.getDimension()) {
|
||||
case NETHER: template = createNetherMapTemplate(name, worldFolder, i - 1); break;
|
||||
case END: template = createEndMapTemplate(name, worldFolder, i - 1); break;
|
||||
default: template = createOverworldMapTemplate(name, worldFolder, i - 1); break;
|
||||
}
|
||||
ConfigTemplate template = switch (world.getDimension().getFormatted()) {
|
||||
case "minecraft:the_nether" -> createNetherMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
case "minecraft:the_end" -> createEndMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
default -> createOverworldMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
};
|
||||
|
||||
Files.writeString(
|
||||
configFile,
|
||||
|
@ -280,8 +297,7 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
try (Stream<Path> configFiles = Files.list(mapConfigFolder)) {
|
||||
for (var configFile : configFiles.toArray(Path[]::new)) {
|
||||
if (!configManager.isConfigFile(configFile)) continue;
|
||||
Path rawConfig = configManager.getRaw(configFile);
|
||||
String id = sanitiseMapId(rawConfig.getFileName().toString());
|
||||
String id = sanitiseMapId(configManager.getConfigName(configFile));
|
||||
|
||||
if (mapConfigs.containsKey(id)) {
|
||||
throw new ConfigurationException("At least two of your map-config file-names result in ambiguous map-id's!\n" +
|
||||
|
@ -289,7 +305,7 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
"To resolve this issue, rename this file to something else.");
|
||||
}
|
||||
|
||||
MapConfig mapConfig = configManager.loadConfig(rawConfig, MapConfig.class);
|
||||
MapConfig mapConfig = configManager.loadConfig(configFile, MapConfig.class);
|
||||
mapConfigs.put(id, mapConfig);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
|
@ -302,25 +318,24 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
return mapConfigs;
|
||||
}
|
||||
|
||||
private synchronized Map<String, StorageConfig> loadStorageConfigs(Path defaultWebroot) throws ConfigurationException {
|
||||
private Map<String, StorageConfig> loadStorageConfigs(Path defaultWebroot) throws ConfigurationException {
|
||||
Map<String, StorageConfig> storageConfigs = new HashMap<>();
|
||||
|
||||
Path storageFolder = Paths.get("storages");
|
||||
Path storageConfigFolder = configManager.getConfigRoot().resolve(storageFolder);
|
||||
Path storageConfigFolder = configManager.getConfigRoot().resolve(STORAGES_CONFIG_FOLDER_NAME);
|
||||
|
||||
if (!Files.exists(storageConfigFolder)){
|
||||
try {
|
||||
FileHelper.createDirectories(storageConfigFolder);
|
||||
Files.writeString(
|
||||
storageConfigFolder.resolve("file.conf"),
|
||||
configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/storages/file.conf")
|
||||
configManager.resolveConfigFile(FILE_STORAGE_CONFIG_NAME),
|
||||
configManager.loadConfigTemplate(FILE_STORAGE_CONFIG_NAME)
|
||||
.setVariable("root", formatPath(defaultWebroot.resolve("maps")))
|
||||
.build(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||
);
|
||||
Files.writeString(
|
||||
storageConfigFolder.resolve("sql.conf"),
|
||||
configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/storages/sql.conf").build(),
|
||||
configManager.resolveConfigFile(SQL_STORAGE_CONFIG_NAME),
|
||||
configManager.loadConfigTemplate(SQL_STORAGE_CONFIG_NAME).build(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
||||
);
|
||||
} catch (IOException | NullPointerException ex) {
|
||||
|
@ -335,11 +350,10 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
try (Stream<Path> configFiles = Files.list(storageConfigFolder)) {
|
||||
for (var configFile : configFiles.toArray(Path[]::new)) {
|
||||
if (!configManager.isConfigFile(configFile)) continue;
|
||||
Path rawConfig = configManager.getRaw(configFile);
|
||||
String id = rawConfig.getFileName().toString();
|
||||
String id = configManager.getConfigName(configFile);
|
||||
|
||||
StorageConfig storageConfig = configManager.loadConfig(rawConfig, StorageConfig.class); // load superclass
|
||||
storageConfig = configManager.loadConfig(rawConfig, storageConfig.getStorageType().getConfigType()); // load actual config type
|
||||
StorageConfig storageConfig = configManager.loadConfig(configFile, StorageConfig.Base.class); // load superclass
|
||||
storageConfig = configManager.loadConfig(configFile, storageConfig.getStorageType().getConfigType()); // load actual config type
|
||||
|
||||
storageConfigs.put(id, storageConfig);
|
||||
}
|
||||
|
@ -357,40 +371,43 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
|
|||
return id.replaceAll("\\W", "_");
|
||||
}
|
||||
|
||||
private ConfigTemplate createOverworldMapTemplate(String name, Path worldFolder, int index) throws IOException {
|
||||
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
||||
private ConfigTemplate createOverworldMapTemplate(String name, Path worldFolder, Key dimension, int index) throws IOException {
|
||||
return configManager.loadConfigTemplate(MAP_STORAGE_CONFIG_NAME)
|
||||
.setVariable("name", name)
|
||||
.setVariable("sorting", "" + index)
|
||||
.setVariable("world", formatPath(worldFolder))
|
||||
.setVariable("dimension", dimension.getFormatted())
|
||||
.setVariable("sky-color", "#7dabff")
|
||||
.setVariable("void-color", "#000000")
|
||||
.setVariable("ambient-light", "0.1")
|
||||
.setVariable("world-sky-light", "15")
|
||||
.setVariable("remove-caves-below-y", "55")
|
||||
.setConditional("max-y-comment", true)
|
||||
.setVariable("max-y", "100");
|
||||
}
|
||||
|
||||
private ConfigTemplate createNetherMapTemplate(String name, Path worldFolder, int index) throws IOException {
|
||||
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
||||
private ConfigTemplate createNetherMapTemplate(String name, Path worldFolder, Key dimension, int index) throws IOException {
|
||||
return configManager.loadConfigTemplate(MAP_STORAGE_CONFIG_NAME)
|
||||
.setVariable("name", name)
|
||||
.setVariable("sorting", "" + (100 + index))
|
||||
.setVariable("world", formatPath(worldFolder))
|
||||
.setVariable("dimension", dimension.getFormatted())
|
||||
.setVariable("sky-color", "#290000")
|
||||
.setVariable("void-color", "#150000")
|
||||
.setVariable("ambient-light", "0.6")
|
||||
.setVariable("world-sky-light", "0")
|
||||
.setVariable("remove-caves-below-y", "-10000")
|
||||
.setConditional("max-y-comment", false)
|
||||
.setVariable("max-y", "90");
|
||||
}
|
||||
|
||||
private ConfigTemplate createEndMapTemplate(String name, Path worldFolder, int index) throws IOException {
|
||||
return configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/maps/map.conf")
|
||||
private ConfigTemplate createEndMapTemplate(String name, Path worldFolder, Key dimension, int index) throws IOException {
|
||||
return configManager.loadConfigTemplate(MAP_STORAGE_CONFIG_NAME)
|
||||
.setVariable("name", name)
|
||||
.setVariable("sorting", "" + (200 + index))
|
||||
.setVariable("world", formatPath(worldFolder))
|
||||
.setVariable("dimension", dimension.getFormatted())
|
||||
.setVariable("sky-color", "#080010")
|
||||
.setVariable("void-color", "#080010")
|
||||
.setVariable("ambient-light", "0.6")
|
||||
.setVariable("world-sky-light", "0")
|
||||
.setVariable("remove-caves-below-y", "-10000")
|
||||
.setConditional("max-y-comment", true)
|
||||
.setVariable("max-y", "100");
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.config;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Keyed;
|
||||
import de.bluecolored.bluemap.core.util.Registry;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
|
||||
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface ConfigLoader extends Keyed {
|
||||
|
||||
ConfigLoader HOCON = new Impl(Key.bluemap("hocon"), ".conf", HoconConfigurationLoader::builder);
|
||||
ConfigLoader JSON = new Impl(Key.bluemap("json"), ".json", GsonConfigurationLoader::builder);
|
||||
|
||||
ConfigLoader DEFAULT = HOCON;
|
||||
|
||||
Registry<ConfigLoader> REGISTRY = new Registry<>(
|
||||
HOCON,
|
||||
JSON
|
||||
);
|
||||
|
||||
String getFileSuffix();
|
||||
|
||||
AbstractConfigurationLoader.Builder<?, ?> createLoaderBuilder();
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
class Impl implements ConfigLoader {
|
||||
|
||||
private final Key key;
|
||||
private final String fileSuffix;
|
||||
private final Supplier<AbstractConfigurationLoader.Builder<?, ?>> builderSupplier;
|
||||
|
||||
@Override
|
||||
public AbstractConfigurationLoader.Builder<?, ?> createLoaderBuilder() {
|
||||
return builderSupplier.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -25,30 +25,28 @@
|
|||
package de.bluecolored.bluemap.common.config;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.common.config.typeserializer.KeyTypeSerializer;
|
||||
import de.bluecolored.bluemap.common.config.typeserializer.Vector2iTypeSerializer;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
|
||||
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
|
||||
import org.spongepowered.configurate.loader.ConfigurationLoader;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ConfigManager {
|
||||
|
||||
private static final String[] CONFIG_FILE_ENDINGS = new String[] {
|
||||
".conf",
|
||||
".json"
|
||||
};
|
||||
private static final String CONFIG_TEMPLATE_RESOURCE_PATH = "/de/bluecolored/bluemap/config/";
|
||||
|
||||
private final Path configRoot;
|
||||
|
||||
|
@ -56,30 +54,66 @@ public class ConfigManager {
|
|||
this.configRoot = configRoot;
|
||||
}
|
||||
|
||||
public <T> T loadConfig(Path rawPath, Class<T> type) throws ConfigurationException {
|
||||
Path path = findConfigPath(rawPath);
|
||||
ConfigurationNode configNode = loadConfigFile(path);
|
||||
public <T> T loadConfig(String name, Class<T> type) throws ConfigurationException {
|
||||
Path file = resolveConfigFile(name);
|
||||
return loadConfig(file, type);
|
||||
}
|
||||
|
||||
public <T> T loadConfig(Path file, Class<T> type) throws ConfigurationException {
|
||||
ConfigurationNode configNode = loadConfigFile(file);
|
||||
try {
|
||||
return Objects.requireNonNull(configNode.get(type));
|
||||
} catch (SerializationException | NullPointerException ex) {
|
||||
throw new ConfigurationException(
|
||||
"BlueMap failed to parse this file:\n" +
|
||||
path + "\n" +
|
||||
file + "\n" +
|
||||
"Check if the file is correctly formatted and all values are correct!",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigurationNode loadConfig(Path rawPath) throws ConfigurationException {
|
||||
Path path = findConfigPath(rawPath);
|
||||
return loadConfigFile(path);
|
||||
public ConfigTemplate loadConfigTemplate(String name) throws IOException {
|
||||
String resource = CONFIG_TEMPLATE_RESOURCE_PATH + name + ConfigLoader.DEFAULT.getFileSuffix();
|
||||
try (InputStream in = BlueMap.class.getResourceAsStream(resource)) {
|
||||
if (in == null) throw new IOException("Resource not found: " + resource);
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
||||
reader.transferTo(writer);
|
||||
|
||||
return new ConfigTemplate(writer.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigTemplate loadConfigTemplate(String resource) throws IOException {
|
||||
InputStream in = BlueMap.class.getResourceAsStream(resource);
|
||||
if (in == null) throw new IOException("Resource not found: " + resource);
|
||||
String configTemplate = IOUtils.toString(in, StandardCharsets.UTF_8);
|
||||
return new ConfigTemplate(configTemplate);
|
||||
public Path resolveConfigFile(String name) {
|
||||
for (ConfigLoader configLoader : ConfigLoader.REGISTRY.values()) {
|
||||
Path path = configRoot.resolve(name + configLoader.getFileSuffix());
|
||||
if (Files.isRegularFile(path)) return path;
|
||||
}
|
||||
|
||||
return configRoot.resolve(name + ConfigLoader.DEFAULT.getFileSuffix());
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isConfigFile(Path file) {
|
||||
String fileName = file.getFileName().toString();
|
||||
for (ConfigLoader configLoader : ConfigLoader.REGISTRY.values())
|
||||
if (fileName.endsWith(configLoader.getFileSuffix())) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getConfigName(Path file) {
|
||||
String fileName = file.getFileName().toString();
|
||||
for (ConfigLoader configLoader : ConfigLoader.REGISTRY.values()) {
|
||||
String suffix = configLoader.getFileSuffix();
|
||||
if (fileName.endsWith(suffix))
|
||||
return fileName.substring(0, fileName.length() - suffix.length());
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public Path getConfigRoot() {
|
||||
return configRoot;
|
||||
}
|
||||
|
||||
private ConfigurationNode loadConfigFile(Path path) throws ConfigurationException {
|
||||
|
@ -108,63 +142,23 @@ public class ConfigManager {
|
|||
}
|
||||
}
|
||||
|
||||
public Path getConfigRoot() {
|
||||
return configRoot;
|
||||
}
|
||||
|
||||
public Path findConfigPath(Path rawPath) {
|
||||
if (!rawPath.startsWith(configRoot))
|
||||
rawPath = configRoot.resolve(rawPath);
|
||||
|
||||
for (String fileEnding : CONFIG_FILE_ENDINGS) {
|
||||
if (rawPath.getFileName().endsWith(fileEnding)) return rawPath;
|
||||
}
|
||||
|
||||
for (String fileEnding : CONFIG_FILE_ENDINGS) {
|
||||
Path path = rawPath.getParent().resolve(rawPath.getFileName() + fileEnding);
|
||||
if (Files.exists(path)) return path;
|
||||
}
|
||||
|
||||
return rawPath.getParent().resolve(rawPath.getFileName() + CONFIG_FILE_ENDINGS[0]);
|
||||
}
|
||||
|
||||
public boolean isConfigFile(Path path) {
|
||||
if (!Files.isRegularFile(path)) return false;
|
||||
|
||||
String fileName = path.getFileName().toString();
|
||||
for (String fileEnding : CONFIG_FILE_ENDINGS) {
|
||||
if (fileName.endsWith(fileEnding)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Path getRaw(Path path) {
|
||||
String fileName = path.getFileName().toString();
|
||||
String rawName = null;
|
||||
|
||||
for (String fileEnding : CONFIG_FILE_ENDINGS) {
|
||||
if (fileName.endsWith(fileEnding)) {
|
||||
rawName = fileName.substring(0, fileName.length() - fileEnding.length());
|
||||
private ConfigurationLoader<? extends ConfigurationNode> getLoader(Path path){
|
||||
AbstractConfigurationLoader.Builder<?, ?> builder = null;
|
||||
for (ConfigLoader loader : ConfigLoader.REGISTRY.values()) {
|
||||
if (path.getFileName().endsWith(loader.getFileSuffix())) {
|
||||
builder = loader.createLoaderBuilder();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rawName == null) return path;
|
||||
return path.getParent().resolve(rawName);
|
||||
}
|
||||
|
||||
private ConfigurationLoader<? extends ConfigurationNode> getLoader(Path path){
|
||||
AbstractConfigurationLoader.Builder<?, ?> builder;
|
||||
if (path.getFileName().endsWith(".json"))
|
||||
builder = GsonConfigurationLoader.builder();
|
||||
else
|
||||
builder = HoconConfigurationLoader.builder();
|
||||
if (builder == null)
|
||||
builder = ConfigLoader.DEFAULT.createLoaderBuilder();
|
||||
|
||||
return builder
|
||||
.path(path)
|
||||
.defaultOptions(o -> o.serializers(b -> {
|
||||
b.register(Vector2i.class, new Vector2iTypeSerializer());
|
||||
b.register(Key.class, new KeyTypeSerializer());
|
||||
}))
|
||||
.build();
|
||||
}
|
||||
|
|
|
@ -24,13 +24,11 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class CoreConfig {
|
||||
|
||||
|
@ -75,7 +73,6 @@ public class CoreConfig {
|
|||
return log;
|
||||
}
|
||||
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public static class LogConfig {
|
||||
|
||||
|
|
|
@ -26,44 +26,45 @@ package de.bluecolored.bluemap.common.config;
|
|||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.map.MapSettings;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
@Getter
|
||||
public class MapConfig implements MapSettings {
|
||||
|
||||
private String name = null;
|
||||
@Nullable private Path world = null;
|
||||
@Nullable private Key dimension = null;
|
||||
|
||||
private Path world = null;
|
||||
@Nullable private String name = null;
|
||||
|
||||
private int sorting = 0;
|
||||
|
||||
private Vector2i startPos = null;
|
||||
@Nullable private Vector2i startPos = null;
|
||||
|
||||
private String skyColor = "#7dabff";
|
||||
private String voidColor = "#000000";
|
||||
|
||||
private float ambientLight = 0;
|
||||
|
||||
private int worldSkyLight = 15;
|
||||
|
||||
private int removeCavesBelowY = 55;
|
||||
private int caveDetectionOceanFloor = 10000;
|
||||
private boolean caveDetectionUsesBlockLight = false;
|
||||
|
||||
private int minX = Integer.MIN_VALUE;
|
||||
private int maxX = Integer.MAX_VALUE;
|
||||
private int minZ = Integer.MIN_VALUE;
|
||||
private int maxZ = Integer.MAX_VALUE;
|
||||
private int minY = Integer.MIN_VALUE;
|
||||
private int maxY = Integer.MAX_VALUE;
|
||||
@Getter(AccessLevel.NONE) private int minX = Integer.MIN_VALUE;
|
||||
@Getter(AccessLevel.NONE) private int maxX = Integer.MAX_VALUE;
|
||||
@Getter(AccessLevel.NONE) private int minZ = Integer.MIN_VALUE;
|
||||
@Getter(AccessLevel.NONE) private int maxZ = Integer.MAX_VALUE;
|
||||
@Getter(AccessLevel.NONE) private int minY = Integer.MIN_VALUE;
|
||||
@Getter(AccessLevel.NONE) private int maxY = Integer.MAX_VALUE;
|
||||
|
||||
private transient Vector3i min = null;
|
||||
private transient Vector3i max = null;
|
||||
|
@ -79,7 +80,7 @@ public class MapConfig implements MapSettings {
|
|||
|
||||
private boolean ignoreMissingLightData = false;
|
||||
|
||||
private ConfigurationNode markerSets = null;
|
||||
@Nullable private ConfigurationNode markerSets = null;
|
||||
|
||||
// hidden config fields
|
||||
private int hiresTileSize = 32;
|
||||
|
@ -87,56 +88,6 @@ public class MapConfig implements MapSettings {
|
|||
private int lodCount = 3;
|
||||
private int lodFactor = 5;
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Path getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSorting() {
|
||||
return sorting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Vector2i> getStartPos() {
|
||||
return Optional.ofNullable(startPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSkyColor() {
|
||||
return skyColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAmbientLight() {
|
||||
return ambientLight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldSkyLight() {
|
||||
return worldSkyLight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoveCavesBelowY() {
|
||||
return removeCavesBelowY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCaveDetectionUsesBlockLight() {
|
||||
return caveDetectionUsesBlockLight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCaveDetectionOceanFloor() {
|
||||
return caveDetectionOceanFloor;
|
||||
}
|
||||
|
||||
public Vector3i getMinPos() {
|
||||
if (min == null) min = new Vector3i(minX, minY, minZ);
|
||||
return min;
|
||||
|
@ -147,57 +98,4 @@ public class MapConfig implements MapSettings {
|
|||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinInhabitedTime() {
|
||||
return minInhabitedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinInhabitedTimeRadius() {
|
||||
return minInhabitedTimeRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderEdges() {
|
||||
return renderEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSaveHiresLayer() {
|
||||
return saveHiresLayer;
|
||||
}
|
||||
|
||||
public String getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public boolean isIgnoreMissingLightData() {
|
||||
return ignoreMissingLightData;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ConfigurationNode getMarkerSets() {
|
||||
return markerSets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHiresTileSize() {
|
||||
return hiresTileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLowresTileSize() {
|
||||
return lowresTileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLodCount() {
|
||||
return lodCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLodFactor() {
|
||||
return lodFactor;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,14 +24,12 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class PluginConfig {
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
@ -33,7 +32,6 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class WebappConfig {
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
@ -33,7 +32,6 @@ import java.net.UnknownHostException;
|
|||
import java.nio.file.Path;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class WebserverConfig {
|
||||
|
||||
|
@ -75,7 +73,6 @@ public class WebserverConfig {
|
|||
return log;
|
||||
}
|
||||
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public static class LogConfig {
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.MySQLCommandSet;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.PostgreSQLCommandSet;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.SqliteCommandSet;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Keyed;
|
||||
import de.bluecolored.bluemap.core.util.Registry;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface Dialect extends Keyed {
|
||||
|
||||
Dialect MYSQL = new Impl(Key.bluemap("mysql"), "jdbc:mysql:", MySQLCommandSet::new);
|
||||
Dialect MARIADB = new Impl(Key.bluemap("mariadb"), "jdbc:mariadb:", MySQLCommandSet::new);
|
||||
Dialect POSTGRESQL = new Impl(Key.bluemap("postgresql"), "jdbc:postgresql:", PostgreSQLCommandSet::new);
|
||||
Dialect SQLITE = new Impl(Key.bluemap("sqlite"), "jdbc:sqlite:", SqliteCommandSet::new);
|
||||
|
||||
Registry<Dialect> REGISTRY = new Registry<>(
|
||||
MYSQL,
|
||||
MARIADB,
|
||||
POSTGRESQL,
|
||||
SQLITE
|
||||
);
|
||||
|
||||
boolean supports(String connectionUrl);
|
||||
|
||||
CommandSet createCommandSet(Database database);
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class Impl implements Dialect {
|
||||
|
||||
@Getter private final Key key;
|
||||
private final String protocol;
|
||||
|
||||
private final Function<Database, CommandSet> commandSetProvider;
|
||||
|
||||
@Override
|
||||
public boolean supports(String connectionUrl) {
|
||||
return connectionUrl.startsWith(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandSet createCommandSet(Database database) {
|
||||
return commandSetProvider.apply(database);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -24,30 +24,30 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.file.FileStorageSettings;
|
||||
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.file.FileStorage;
|
||||
import lombok.Getter;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class FileConfig extends StorageConfig implements FileStorageSettings {
|
||||
@Getter
|
||||
public class FileConfig extends StorageConfig {
|
||||
|
||||
private Path root = Path.of("bluemap", "web", "maps");
|
||||
private String compression = Compression.GZIP.getKey().getFormatted();
|
||||
private boolean atomic = true;
|
||||
|
||||
private Compression compression = Compression.GZIP;
|
||||
|
||||
@Override
|
||||
public Path getRoot() {
|
||||
return root;
|
||||
public Compression getCompression() throws ConfigurationException {
|
||||
return parseKey(Compression.REGISTRY, compression, "compression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Compression getCompression() {
|
||||
return compression;
|
||||
public FileStorage createStorage() throws ConfigurationException {
|
||||
return new FileStorage(root, getCompression(), atomic);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,68 +24,153 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.SQLStorageSettings;
|
||||
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||
import de.bluecolored.bluemap.common.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||||
import de.bluecolored.bluemap.core.storage.sql.SQLStorage;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Driver;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@ConfigSerializable
|
||||
public class SQLConfig extends StorageConfig implements SQLStorageSettings {
|
||||
@Getter
|
||||
public class SQLConfig extends StorageConfig {
|
||||
|
||||
@DebugDump private String driverJar = null;
|
||||
@DebugDump private String driverClass = null;
|
||||
@DebugDump(exclude = true)
|
||||
private String connectionUrl = "jdbc:mysql://localhost/bluemap?permitMysqlScheme";
|
||||
|
||||
@DebugDump(exclude = true)
|
||||
private Map<String, String> connectionProperties = new HashMap<>();
|
||||
|
||||
@DebugDump private Compression compression = Compression.GZIP;
|
||||
private String dialect = null;
|
||||
|
||||
@DebugDump private transient URL driverJarURL = null;
|
||||
private String driverJar = null;
|
||||
private String driverClass = null;
|
||||
private int maxConnections = -1;
|
||||
|
||||
@DebugDump private int maxConnections = -1;
|
||||
private String compression = Compression.GZIP.getKey().getFormatted();
|
||||
|
||||
@Override
|
||||
public Optional<URL> getDriverJar() throws MalformedURLException {
|
||||
if (driverJar == null) return Optional.empty();
|
||||
@Getter(AccessLevel.NONE)
|
||||
private transient URL driverJarURL = null;
|
||||
|
||||
if (driverJarURL == null) {
|
||||
driverJarURL = Paths.get(driverJar).toUri().toURL();
|
||||
public Optional<URL> getDriverJar() throws ConfigurationException {
|
||||
try {
|
||||
if (driverJar == null) return Optional.empty();
|
||||
|
||||
if (driverJarURL == null) {
|
||||
driverJarURL = Paths.get(driverJar).toUri().toURL();
|
||||
}
|
||||
|
||||
return Optional.of(driverJarURL);
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new ConfigurationException("""
|
||||
The configured driver-jar path is not formatted correctly!
|
||||
Please check your 'driver-jar' setting in your configuration and make sure you have the correct path configured.
|
||||
""".strip(), ex);
|
||||
}
|
||||
|
||||
return Optional.of(driverJarURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unused")
|
||||
public Optional<String> getDriverClass() {
|
||||
return Optional.ofNullable(driverClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionUrl() {
|
||||
return connectionUrl;
|
||||
public Compression getCompression() throws ConfigurationException {
|
||||
return parseKey(Compression.REGISTRY, compression, "compression");
|
||||
}
|
||||
|
||||
public Dialect getDialect() throws ConfigurationException {
|
||||
String key = dialect;
|
||||
|
||||
// default from connection-url
|
||||
if (key == null) {
|
||||
for (Dialect d : Dialect.REGISTRY.values()) {
|
||||
if (d.supports(connectionUrl)) {
|
||||
key = d.getKey().getFormatted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == null) throw new ConfigurationException("""
|
||||
Could not find any sql-dialect that is matching the given connection-url.
|
||||
Please check your 'connection-url' setting in your configuration and make sure it is in the correct format.
|
||||
""".strip());
|
||||
}
|
||||
|
||||
return parseKey(Dialect.REGISTRY, key, "dialect");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getConnectionProperties() {
|
||||
return connectionProperties;
|
||||
public SQLStorage createStorage() throws ConfigurationException {
|
||||
Driver driver = createDriver();
|
||||
Database database;
|
||||
if (driver != null) {
|
||||
database = new Database(getConnectionUrl(), getConnectionProperties(), getMaxConnections(), driver);
|
||||
} else {
|
||||
database = new Database(getConnectionUrl(), getConnectionProperties(), getMaxConnections());
|
||||
}
|
||||
CommandSet commandSet = getDialect().createCommandSet(database);
|
||||
return new SQLStorage(commandSet, getCompression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
private @Nullable Driver createDriver() throws ConfigurationException {
|
||||
if (driverClass == null) return null;
|
||||
|
||||
@Override
|
||||
public Compression getCompression() {
|
||||
return compression;
|
||||
try {
|
||||
// load driver class
|
||||
Class<?> driverClazz;
|
||||
URL driverJarUrl = getDriverJar().orElse(null);
|
||||
if (driverJarUrl != null) {
|
||||
|
||||
// sanity-check if file exists
|
||||
if (!Files.exists(Path.of(driverJarUrl.toURI()))) {
|
||||
throw new ConfigurationException("""
|
||||
The configured driver-jar was not found!
|
||||
Please check your 'driver-jar' setting in your configuration and make sure you have the correct path configured.
|
||||
""".strip());
|
||||
}
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[]{driverJarUrl});
|
||||
driverClazz = Class.forName(driverClass, true, classLoader);
|
||||
} else {
|
||||
driverClazz = Class.forName(driverClass);
|
||||
}
|
||||
|
||||
// create driver
|
||||
return (Driver) driverClazz.getDeclaredConstructor().newInstance();
|
||||
} catch (ClassCastException ex) {
|
||||
throw new ConfigurationException("""
|
||||
The configured driver-class was found but is not of the correct class-type!
|
||||
Please check your 'driver-class' setting in your configuration and make sure you have the correct class configured.
|
||||
""".strip(), ex);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new ConfigurationException("""
|
||||
The configured driver-class was not found!
|
||||
Please check your 'driver-class' setting in your configuration and make sure you have the correct class configured.
|
||||
""".strip(), ex);
|
||||
} catch (ConfigurationException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new ConfigurationException("""
|
||||
BlueMap failed to load the configured SQL-Driver!
|
||||
Please check your 'driver-jar' and 'driver-class' settings in your configuration.
|
||||
""".strip(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,26 +24,50 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Keyed;
|
||||
import de.bluecolored.bluemap.core.util.Registry;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class StorageConfig {
|
||||
public abstract class StorageConfig {
|
||||
|
||||
private StorageType storageType = StorageType.FILE;
|
||||
private String storageType = StorageType.FILE.getKey().getFormatted();
|
||||
|
||||
public StorageType getStorageType() {
|
||||
return storageType;
|
||||
public StorageType getStorageType() throws ConfigurationException {
|
||||
return parseKey(StorageType.REGISTRY, storageType, "storage-type");
|
||||
}
|
||||
|
||||
public Storage createStorage() throws Exception {
|
||||
if (this.getClass().equals(StorageConfig.class))
|
||||
throw new UnsupportedOperationException("Can not create a Storage from the StorageConfig superclass.");
|
||||
public abstract Storage createStorage() throws ConfigurationException;
|
||||
|
||||
static <T extends Keyed> T parseKey(Registry<T> registry, String key, String typeName) throws ConfigurationException {
|
||||
T type = registry.get(Key.parse(key, Key.BLUEMAP_NAMESPACE));
|
||||
|
||||
if (type == null) {
|
||||
// try legacy config format
|
||||
Key legacyFormatKey = Key.bluemap(key.toLowerCase(Locale.ROOT));
|
||||
type = registry.get(legacyFormatKey);
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
throw new ConfigurationException("No " + typeName + " found for key: " + key + "!");
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Base extends StorageConfig {
|
||||
|
||||
@Override
|
||||
public Storage createStorage() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
return storageType.getStorageFactory(this.getClass()).provide(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,41 +24,31 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.file.FileStorage;
|
||||
import de.bluecolored.bluemap.core.storage.sql.SQLStorage;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Keyed;
|
||||
import de.bluecolored.bluemap.core.util.Registry;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
public enum StorageType {
|
||||
public interface StorageType extends Keyed {
|
||||
|
||||
FILE (FileConfig.class, FileStorage::new),
|
||||
SQL (SQLConfig.class, SQLStorage::create);
|
||||
StorageType FILE = new Impl(Key.bluemap("file"), FileConfig.class);
|
||||
StorageType SQL = new Impl(Key.bluemap("sql"), SQLConfig.class);
|
||||
|
||||
private final Class<? extends StorageConfig> configType;
|
||||
private final StorageFactory<? extends StorageConfig> storageFactory;
|
||||
Registry<StorageType> REGISTRY = new Registry<>(
|
||||
FILE,
|
||||
SQL
|
||||
);
|
||||
|
||||
<C extends StorageConfig> StorageType(Class<C> configType, StorageFactory<C> storageFactory) {
|
||||
this.configType = configType;
|
||||
this.storageFactory = storageFactory;
|
||||
}
|
||||
Class<? extends StorageConfig> getConfigType();
|
||||
|
||||
public Class<? extends StorageConfig> getConfigType() {
|
||||
return configType;
|
||||
}
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
class Impl implements StorageType {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C extends StorageConfig> StorageFactory<C> getStorageFactory(Class<C> configType) {
|
||||
if (!configType.isAssignableFrom(this.configType)) throw new ClassCastException(this.configType + " can not be cast to " + configType);
|
||||
return (StorageFactory<C>) storageFactory;
|
||||
}
|
||||
private final Key key;
|
||||
private final Class<? extends StorageConfig> configType;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StorageFactory<C extends StorageConfig> {
|
||||
Storage provideRaw(C config) throws Exception;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default Storage provide(StorageConfig config) throws Exception {
|
||||
return provideRaw((C) config);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,40 +22,27 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.threejs;
|
||||
package de.bluecolored.bluemap.common.config.typeserializer;
|
||||
|
||||
public class MaterialGroup {
|
||||
private int materialIndex;
|
||||
private int start;
|
||||
private int count;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
|
||||
public MaterialGroup(int materialIndex, int start, int count) {
|
||||
this.materialIndex = materialIndex;
|
||||
this.start = start;
|
||||
this.count = count;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class KeyTypeSerializer implements TypeSerializer<Key> {
|
||||
|
||||
@Override
|
||||
public Key deserialize(Type type, ConfigurationNode node) {
|
||||
String formatted = node.getString();
|
||||
return formatted != null ? new Key(node.getString()) : null;
|
||||
}
|
||||
|
||||
public int getMaterialIndex() {
|
||||
return materialIndex;
|
||||
@Override
|
||||
public void serialize(Type type, @Nullable Key obj, ConfigurationNode node) throws SerializationException {
|
||||
if (obj != null) node.set(obj.getFormatted());
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setMaterialIndex(int materialIndex) {
|
||||
this.materialIndex = materialIndex;
|
||||
}
|
||||
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.debug;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({
|
||||
ElementType.METHOD,
|
||||
ElementType.FIELD,
|
||||
ElementType.TYPE
|
||||
})
|
||||
public @interface DebugDump {
|
||||
|
||||
String value() default "";
|
||||
|
||||
boolean exclude() default false;
|
||||
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* 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.debug;
|
||||
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
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 {
|
||||
JsonWriter writer = new JsonWriter(Files.newBufferedWriter(
|
||||
file,
|
||||
StandardCharsets.UTF_8,
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING
|
||||
));
|
||||
writer.setIndent(" ");
|
||||
|
||||
writer.beginObject();
|
||||
|
||||
writer.name("system-info");
|
||||
collectSystemInfo(writer);
|
||||
|
||||
Set<Object> alreadyDumped = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
|
||||
writer.name("threads").beginArray();
|
||||
for (Thread thread : Thread.getAllStackTraces().keySet()) {
|
||||
dumpInstance(thread, writer, alreadyDumped);
|
||||
}
|
||||
writer.endArray();
|
||||
|
||||
writer.name("dump").beginObject();
|
||||
for (Object instance : instances) {
|
||||
Class<?> type = instance.getClass();
|
||||
writer.name(type.getName());
|
||||
dumpInstance(instance, writer, alreadyDumped);
|
||||
}
|
||||
writer.endObject();
|
||||
|
||||
writer.endObject();
|
||||
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private void dumpInstance(Object instance, JsonWriter writer, Set<Object> alreadyDumped) throws IOException {
|
||||
|
||||
if (instance == null) {
|
||||
writer.nullValue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance instanceof String ||
|
||||
instance instanceof Path ||
|
||||
instance instanceof UUID ||
|
||||
instance instanceof Key
|
||||
) {
|
||||
writer.value(instance.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance instanceof Number val) {
|
||||
writer.value(val);
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance instanceof Boolean val) {
|
||||
writer.value(val);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alreadyDumped.add(instance)) {
|
||||
writer.value("<<" + toIdentityString(instance) + ">>");
|
||||
return;
|
||||
}
|
||||
|
||||
writer.beginObject();
|
||||
try {
|
||||
String identityString = toIdentityString(instance);
|
||||
writer.name("#identity").value(identityString);
|
||||
|
||||
if (instance instanceof Map<?, ?> map) {
|
||||
writer.name("entries").beginArray();
|
||||
|
||||
int count = 0;
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (++count > 30) {
|
||||
writer.value("<<" + (map.size() - 30) + " more elements>>");
|
||||
break;
|
||||
}
|
||||
|
||||
writer.beginObject();
|
||||
|
||||
writer.name("key");
|
||||
dumpInstance(entry.getKey(), writer, alreadyDumped);
|
||||
|
||||
writer.name("value");
|
||||
dumpInstance(entry.getValue(), writer, alreadyDumped);
|
||||
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
writer.endArray();
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance instanceof Collection<?> collection) {
|
||||
writer.name("entries").beginArray();
|
||||
|
||||
int count = 0;
|
||||
for (Object entry : collection) {
|
||||
if (++count > 30) {
|
||||
writer.value("<<" + (collection.size() - 30) + " more elements>>");
|
||||
break;
|
||||
}
|
||||
|
||||
dumpInstance(entry, writer, alreadyDumped);
|
||||
}
|
||||
|
||||
writer.endArray();
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance instanceof Object[] array) {
|
||||
writer.name("entries").beginArray();
|
||||
|
||||
int count = 0;
|
||||
for (Object entry : array) {
|
||||
if (++count > 30) {
|
||||
writer.value("<<" + (array.length - 30) + " more elements>>");
|
||||
break;
|
||||
}
|
||||
|
||||
dumpInstance(entry, writer, alreadyDumped);
|
||||
}
|
||||
|
||||
writer.endArray();
|
||||
return;
|
||||
}
|
||||
|
||||
String toString = instance.toString();
|
||||
if (!toString.equals(identityString))
|
||||
writer.name("#toString").value(instance.toString());
|
||||
|
||||
if (instance instanceof Thread thread) {
|
||||
writer.name("name").value(thread.getName());
|
||||
writer.name("state").value(thread.getState().toString());
|
||||
writer.name("priority").value(thread.getPriority());
|
||||
writer.name("alive").value(thread.isAlive());
|
||||
writer.name("id").value(thread.getId());
|
||||
writer.name("deamon").value(thread.isDaemon());
|
||||
writer.name("interrupted").value(thread.isInterrupted());
|
||||
|
||||
try {
|
||||
StackTraceElement[] trace = thread.getStackTrace();
|
||||
writer.name("stacktrace").beginArray();
|
||||
for (StackTraceElement element : trace) {
|
||||
writer.value(element.toString());
|
||||
}
|
||||
writer.endArray();
|
||||
} catch (SecurityException ignore) {}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dumpAnnotatedInstance(instance.getClass(), instance, writer, alreadyDumped);
|
||||
|
||||
} finally {
|
||||
writer.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
private static String toIdentityString(Object instance) {
|
||||
return instance.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(instance));
|
||||
}
|
||||
|
||||
private void dumpAnnotatedInstance(Class<?> type, Object instance, JsonWriter writer, Set<Object> alreadyDumped) throws IOException {
|
||||
|
||||
DebugDump typedd = type.getAnnotation(DebugDump.class);
|
||||
boolean exclude = typedd != null && typedd.exclude();
|
||||
boolean allFields = !exclude && (
|
||||
typedd != null ||
|
||||
type.getPackageName().startsWith("de.bluecolored.bluemap")
|
||||
);
|
||||
|
||||
for (Field field : type.getDeclaredFields()) {
|
||||
String key = field.getName();
|
||||
Object value;
|
||||
|
||||
try {
|
||||
DebugDump dd = field.getAnnotation(DebugDump.class);
|
||||
if (dd == null) {
|
||||
if (!allFields) continue;
|
||||
if (Modifier.isStatic(field.getModifiers())) continue;
|
||||
if (Modifier.isTransient(field.getModifiers())) continue;
|
||||
} else {
|
||||
if (dd.exclude()) continue;
|
||||
}
|
||||
|
||||
if (dd != null) {
|
||||
key = dd.value();
|
||||
if (key.isEmpty()) key = field.getName();
|
||||
}
|
||||
|
||||
field.setAccessible(true);
|
||||
value = field.get(instance);
|
||||
} catch (Exception ex) {
|
||||
writer.name("!!" + key).value(ex.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
writer.name(key);
|
||||
dumpInstance(value, writer, alreadyDumped);
|
||||
}
|
||||
|
||||
for (Method method : type.getDeclaredMethods()) {
|
||||
String key = method.toGenericString();
|
||||
Object value;
|
||||
|
||||
try {
|
||||
DebugDump dd = method.getAnnotation(DebugDump.class);
|
||||
if (dd == null || dd.exclude()) continue;
|
||||
|
||||
key = dd.value();
|
||||
if (key.isEmpty()) key = method.toGenericString();
|
||||
|
||||
method.setAccessible(true);
|
||||
value = method.invoke(instance);
|
||||
} catch (Exception ex) {
|
||||
writer.name("!!" + key).value(ex.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
writer.name(key);
|
||||
dumpInstance(value, writer, alreadyDumped);
|
||||
}
|
||||
|
||||
for (Class<?> iface : type.getInterfaces()) {
|
||||
dumpAnnotatedInstance(iface, instance, writer, alreadyDumped);
|
||||
}
|
||||
|
||||
Class<?> typeSuperclass = type.getSuperclass();
|
||||
if (typeSuperclass != null) {
|
||||
dumpAnnotatedInstance(typeSuperclass, instance, writer, alreadyDumped);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void collectSystemInfo(JsonWriter writer) throws IOException {
|
||||
writer.beginObject();
|
||||
|
||||
writer.name("bluemap-version").value(BlueMap.VERSION);
|
||||
writer.name("git-hash").value(BlueMap.GIT_HASH);
|
||||
|
||||
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));
|
||||
}
|
||||
writer.name("properties");
|
||||
dumpInstance(propMap, writer, new HashSet<>());
|
||||
|
||||
writer.name("cores").value(Runtime.getRuntime().availableProcessors());
|
||||
writer.name("max-memory").value(Runtime.getRuntime().maxMemory());
|
||||
writer.name("total-memory").value(Runtime.getRuntime().totalMemory());
|
||||
writer.name("free-memory").value(Runtime.getRuntime().freeMemory());
|
||||
|
||||
writer.name("timestamp").value(System.currentTimeMillis());
|
||||
writer.name("time").value(LocalDateTime.now().toString());
|
||||
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
public static StateDumper global() {
|
||||
return GLOBAL;
|
||||
}
|
||||
|
||||
public synchronized void register(Object instance) {
|
||||
GLOBAL.instances.add(instance);
|
||||
}
|
||||
|
||||
}
|
|
@ -27,9 +27,9 @@ package de.bluecolored.bluemap.common.live;
|
|||
import com.google.gson.stream.JsonWriter;
|
||||
import de.bluecolored.bluemap.common.config.PluginConfig;
|
||||
import de.bluecolored.bluemap.common.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.common.serverinterface.Server;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
@ -39,15 +39,15 @@ import java.util.function.Supplier;
|
|||
|
||||
public class LivePlayersDataSupplier implements Supplier<String> {
|
||||
|
||||
private final ServerInterface server;
|
||||
private final Server server;
|
||||
private final PluginConfig config;
|
||||
@Nullable private final String worldId;
|
||||
private final ServerWorld world;
|
||||
private final Predicate<UUID> playerFilter;
|
||||
|
||||
public LivePlayersDataSupplier(ServerInterface server, PluginConfig config, @Nullable String worldId, Predicate<UUID> playerFilter) {
|
||||
public LivePlayersDataSupplier(Server server, PluginConfig config, ServerWorld world, Predicate<UUID> playerFilter) {
|
||||
this.server = server;
|
||||
this.config = config;
|
||||
this.worldId = worldId;
|
||||
this.world = world;
|
||||
this.playerFilter = playerFilter;
|
||||
}
|
||||
|
||||
|
@ -61,9 +61,7 @@ public class LivePlayersDataSupplier implements Supplier<String> {
|
|||
|
||||
if (config.isLivePlayerMarkers()) {
|
||||
for (Player player : this.server.getOnlinePlayers()) {
|
||||
if (!player.isOnline()) continue;
|
||||
|
||||
boolean isCorrectWorld = player.getWorld().equals(this.worldId);
|
||||
boolean isCorrectWorld = player.getWorld().equals(this.world);
|
||||
|
||||
if (config.isHideInvisible() && player.isInvisible()) continue;
|
||||
if (config.isHideVanished() && player.isVanished()) continue;
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.util.WatchService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class MapUpdateService extends Thread {
|
||||
|
||||
private final BmMap map;
|
||||
private final RenderManager renderManager;
|
||||
private final WatchService<Vector2i> watchService;
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
private Timer delayTimer;
|
||||
|
||||
private final Map<Vector2i, TimerTask> scheduledUpdates;
|
||||
|
||||
public MapUpdateService(RenderManager renderManager, BmMap map) throws IOException {
|
||||
this.renderManager = renderManager;
|
||||
this.map = map;
|
||||
this.closed = false;
|
||||
this.scheduledUpdates = new HashMap<>();
|
||||
this.watchService = map.getWorld().createRegionWatchService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (delayTimer == null) delayTimer = new Timer("BlueMap-RegionFileWatchService-DelayTimer", true);
|
||||
|
||||
Logger.global.logDebug("Started watching map '" + map.getId() + "' for updates...");
|
||||
|
||||
try {
|
||||
while (!closed)
|
||||
this.watchService.take().forEach(this::updateRegion);
|
||||
} catch (WatchService.ClosedException ignore) {
|
||||
} catch (InterruptedException iex) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
Logger.global.logDebug("Stopped watching map '" + map.getId() + "' for updates.");
|
||||
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(Vector2i regionPos) {
|
||||
// we only want to start the render when there were no changes on a file for 5 seconds
|
||||
TimerTask task = scheduledUpdates.remove(regionPos);
|
||||
if (task != null) task.cancel();
|
||||
|
||||
task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (MapUpdateService.this) {
|
||||
WorldRegionRenderTask task = new WorldRegionRenderTask(map, regionPos);
|
||||
scheduledUpdates.remove(regionPos);
|
||||
renderManager.scheduleRenderTask(task);
|
||||
|
||||
Logger.global.logDebug("Scheduled update for region-file: " + regionPos + " (Map: " + map.getId() + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
scheduledUpdates.put(regionPos, task);
|
||||
delayTimer.schedule(task, 5000);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.closed = true;
|
||||
this.interrupt();
|
||||
|
||||
if (this.delayTimer != null) this.delayTimer.cancel();
|
||||
|
||||
try {
|
||||
this.watchService.close();
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logError("Exception while trying to close WatchService!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -24,29 +24,34 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.plugin;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.common.BlueMapConfigProvider;
|
||||
import de.bluecolored.bluemap.common.BlueMapConfiguration;
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.InterruptableReentrantLock;
|
||||
import de.bluecolored.bluemap.common.MissingResourcesException;
|
||||
import de.bluecolored.bluemap.common.addons.Addons;
|
||||
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
|
||||
import de.bluecolored.bluemap.common.config.*;
|
||||
import de.bluecolored.bluemap.common.debug.StateDumper;
|
||||
import de.bluecolored.bluemap.common.live.LivePlayersDataSupplier;
|
||||
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.serverinterface.Server;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.common.web.*;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpServer;
|
||||
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.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import de.bluecolored.bluemap.core.util.Tristate;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
@ -58,6 +63,7 @@ import java.io.Writer;
|
|||
import java.net.BindException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
|
@ -67,7 +73,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@DebugDump
|
||||
@Getter
|
||||
public class Plugin implements ServerEventListener {
|
||||
|
||||
public static final String PLUGIN_ID = "bluemap";
|
||||
|
@ -75,33 +81,28 @@ public class Plugin implements ServerEventListener {
|
|||
|
||||
private static final String DEBUG_FILE_LOG_NAME = "file-debug-log";
|
||||
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final InterruptableReentrantLock loadingLock = new InterruptableReentrantLock();
|
||||
|
||||
private final String implementationType;
|
||||
private final ServerInterface serverInterface;
|
||||
private final Server serverInterface;
|
||||
|
||||
private BlueMapService blueMap;
|
||||
|
||||
private PluginState pluginState;
|
||||
|
||||
private Map<String, World> worlds;
|
||||
private Map<String, BmMap> maps;
|
||||
|
||||
private RenderManager renderManager;
|
||||
private HttpServer webServer;
|
||||
private Logger webLogger;
|
||||
|
||||
private BlueMapAPIImpl api;
|
||||
|
||||
private HttpServer webServer;
|
||||
private RoutingRequestHandler webRequestHandler;
|
||||
private Logger webLogger;
|
||||
|
||||
private Timer daemonTimer;
|
||||
|
||||
private Map<String, RegionFileWatchService> regionFileWatchServices;
|
||||
|
||||
private Map<String, MapUpdateService> mapUpdateServices;
|
||||
private PlayerSkinUpdater skinUpdater;
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
public Plugin(String implementationType, ServerInterface serverInterface) {
|
||||
public Plugin(String implementationType, Server serverInterface) {
|
||||
this.implementationType = implementationType.toLowerCase();
|
||||
this.serverInterface = serverInterface;
|
||||
|
||||
|
@ -120,12 +121,25 @@ public class Plugin implements ServerEventListener {
|
|||
if (loaded) return;
|
||||
unload(); //ensure nothing is left running (from a failed load or something)
|
||||
|
||||
//load addons
|
||||
Path addonsFolder = serverInterface.getConfigFolder().resolve("addons");
|
||||
Files.createDirectories(addonsFolder);
|
||||
Addons.tryLoadAddons(addonsFolder, true);
|
||||
//serverInterface.getModsFolder().ifPresent(Addons::tryLoadAddons);
|
||||
|
||||
//load configs
|
||||
blueMap = new BlueMapService(serverInterface, new BlueMapConfigs(serverInterface), preloadedResourcePack);
|
||||
CoreConfig coreConfig = getConfigs().getCoreConfig();
|
||||
WebserverConfig webserverConfig = getConfigs().getWebserverConfig();
|
||||
WebappConfig webappConfig = getConfigs().getWebappConfig();
|
||||
PluginConfig pluginConfig = getConfigs().getPluginConfig();
|
||||
BlueMapConfigManager configManager = BlueMapConfigManager.builder()
|
||||
.minecraftVersion(serverInterface.getMinecraftVersion())
|
||||
.configRoot(serverInterface.getConfigFolder())
|
||||
.packsFolder(serverInterface.getConfigFolder().resolve("packs"))
|
||||
.modsFolder(serverInterface.getModsFolder().orElse(null))
|
||||
.useMetricsConfig(serverInterface.isMetricsEnabled() == Tristate.UNDEFINED)
|
||||
.autoConfigWorlds(serverInterface.getLoadedServerWorlds())
|
||||
.build();
|
||||
CoreConfig coreConfig = configManager.getCoreConfig();
|
||||
WebserverConfig webserverConfig = configManager.getWebserverConfig();
|
||||
WebappConfig webappConfig = configManager.getWebappConfig();
|
||||
PluginConfig pluginConfig = configManager.getPluginConfig();
|
||||
|
||||
//apply new file-logger config
|
||||
if (coreConfig.getLog().getFile() != null) {
|
||||
|
@ -149,16 +163,19 @@ public class Plugin implements ServerEventListener {
|
|||
pluginState = new PluginState();
|
||||
}
|
||||
|
||||
//create bluemap-service
|
||||
blueMap = new BlueMapService(configManager, preloadedResourcePack);
|
||||
|
||||
//try load resources
|
||||
try {
|
||||
blueMap.getResourcePack();
|
||||
blueMap.getOrLoadResourcePack();
|
||||
} catch (MissingResourcesException ex) {
|
||||
Logger.global.logWarning("BlueMap is missing important resources!");
|
||||
Logger.global.logWarning("You must accept the required file download in order for BlueMap to work!");
|
||||
|
||||
BlueMapConfigProvider configProvider = blueMap.getConfigs();
|
||||
if (configProvider instanceof BlueMapConfigs) {
|
||||
Logger.global.logWarning("Please check: " + ((BlueMapConfigs) configProvider).getConfigManager().findConfigPath(Path.of("core")).toAbsolutePath().normalize());
|
||||
BlueMapConfiguration configProvider = blueMap.getConfig();
|
||||
if (configProvider instanceof BlueMapConfigManager) {
|
||||
Logger.global.logWarning("Please check: " + ((BlueMapConfigManager) configProvider).getConfigManager().resolveConfigFile(BlueMapConfigManager.CORE_CONFIG_NAME).toAbsolutePath().normalize());
|
||||
}
|
||||
|
||||
Logger.global.logInfo("If you have changed the config you can simply reload the plugin using: /bluemap reload");
|
||||
|
@ -167,22 +184,21 @@ public class Plugin implements ServerEventListener {
|
|||
return;
|
||||
}
|
||||
|
||||
//load worlds and maps
|
||||
worlds = blueMap.getWorlds();
|
||||
maps = blueMap.getMaps();
|
||||
//load maps
|
||||
Map<String, BmMap> maps = blueMap.getOrLoadMaps();
|
||||
|
||||
//create and start webserver
|
||||
if (webserverConfig.isEnabled()) {
|
||||
Path webroot = webserverConfig.getWebroot();
|
||||
FileHelper.createDirectories(webroot);
|
||||
|
||||
RoutingRequestHandler routingRequestHandler = new RoutingRequestHandler();
|
||||
this.webRequestHandler = new RoutingRequestHandler();
|
||||
|
||||
// default route
|
||||
routingRequestHandler.register(".*", new FileRequestHandler(webroot));
|
||||
webRequestHandler.register(".*", new FileRequestHandler(webroot));
|
||||
|
||||
// map route
|
||||
for (var mapConfigEntry : getConfigs().getMapConfigs().entrySet()) {
|
||||
for (var mapConfigEntry : configManager.getMapConfigs().entrySet()) {
|
||||
String id = mapConfigEntry.getKey();
|
||||
MapConfig mapConfig = mapConfigEntry.getValue();
|
||||
|
||||
|
@ -191,11 +207,11 @@ public class Plugin implements ServerEventListener {
|
|||
if (map != null) {
|
||||
mapRequestHandler = new MapRequestHandler(map, serverInterface, pluginConfig, Predicate.not(pluginState::isPlayerHidden));
|
||||
} else {
|
||||
Storage storage = blueMap.getStorage(mapConfig.getStorage());
|
||||
mapRequestHandler = new MapRequestHandler(id, storage);
|
||||
Storage storage = blueMap.getOrLoadStorage(mapConfig.getStorage());
|
||||
mapRequestHandler = new MapRequestHandler(storage.map(id));
|
||||
}
|
||||
|
||||
routingRequestHandler.register(
|
||||
webRequestHandler.register(
|
||||
"maps/" + Pattern.quote(id) + "/(.*)",
|
||||
"$1",
|
||||
new BlueMapResponseModifier(mapRequestHandler)
|
||||
|
@ -215,7 +231,7 @@ public class Plugin implements ServerEventListener {
|
|||
|
||||
try {
|
||||
webServer = new HttpServer(new LoggingRequestHandler(
|
||||
routingRequestHandler,
|
||||
webRequestHandler,
|
||||
webserverConfig.getLog().getFormat(),
|
||||
webLogger
|
||||
));
|
||||
|
@ -231,9 +247,11 @@ public class Plugin implements ServerEventListener {
|
|||
throw new ConfigurationException("BlueMap failed to bind to the configured address.\n" +
|
||||
"This usually happens when the configured port (" + webserverConfig.getPort() + ") is already in use by some other program.", ex);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
|
||||
"Check your webserver-config if everything is configured correctly.\n" +
|
||||
"(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)", ex);
|
||||
throw new ConfigurationException("""
|
||||
BlueMap failed to initialize the webserver.
|
||||
Check your webserver-config if everything is configured correctly.
|
||||
(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)
|
||||
""".strip(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,7 +294,7 @@ public class Plugin implements ServerEventListener {
|
|||
save();
|
||||
}
|
||||
};
|
||||
daemonTimer.schedule(saveTask, TimeUnit.MINUTES.toMillis(2), TimeUnit.MINUTES.toMillis(2));
|
||||
daemonTimer.schedule(saveTask, TimeUnit.MINUTES.toMillis(10), TimeUnit.MINUTES.toMillis(10));
|
||||
|
||||
//periodically save markers
|
||||
int writeMarkersInterval = pluginConfig.getWriteMarkersInterval();
|
||||
|
@ -306,8 +324,8 @@ public class Plugin implements ServerEventListener {
|
|||
TimerTask fileWatcherRestartTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
regionFileWatchServices.values().forEach(RegionFileWatchService::close);
|
||||
regionFileWatchServices.clear();
|
||||
mapUpdateServices.values().forEach(MapUpdateService::close);
|
||||
mapUpdateServices.clear();
|
||||
initFileWatcherTasks();
|
||||
}
|
||||
};
|
||||
|
@ -330,17 +348,18 @@ public class Plugin implements ServerEventListener {
|
|||
}
|
||||
|
||||
//metrics
|
||||
MinecraftVersion minecraftVersion = blueMap.getOrLoadMinecraftVersion();
|
||||
TimerTask metricsTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Plugin.this.serverInterface.isMetricsEnabled().getOr(coreConfig::isMetrics))
|
||||
Metrics.sendReport(Plugin.this.implementationType);
|
||||
if (serverInterface.isMetricsEnabled().getOr(coreConfig::isMetrics))
|
||||
Metrics.sendReport(implementationType, minecraftVersion.getId());
|
||||
}
|
||||
};
|
||||
daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30));
|
||||
|
||||
//watch map-changes
|
||||
this.regionFileWatchServices = new HashMap<>();
|
||||
this.mapUpdateServices = new HashMap<>();
|
||||
initFileWatcherTasks();
|
||||
|
||||
//register listener
|
||||
|
@ -350,10 +369,6 @@ public class Plugin implements ServerEventListener {
|
|||
this.api = new BlueMapAPIImpl(this);
|
||||
this.api.register();
|
||||
|
||||
//save webapp settings again (for api-registered scripts and styles)
|
||||
if (webappConfig.isEnabled())
|
||||
this.getBlueMap().getWebFilesManager().saveSettings();
|
||||
|
||||
//start render-manager
|
||||
if (pluginState.isRenderThreadsEnabled()) {
|
||||
checkPausedByPlayerCount(); // <- this also starts the render-manager if it should start
|
||||
|
@ -378,12 +393,11 @@ public class Plugin implements ServerEventListener {
|
|||
public void unload() {
|
||||
this.unload(false);
|
||||
}
|
||||
|
||||
public void unload(boolean keepWebserver) {
|
||||
loadingLock.interruptAndLock();
|
||||
try {
|
||||
synchronized (this) {
|
||||
//save
|
||||
save();
|
||||
|
||||
//disable api
|
||||
if (api != null) api.unregister();
|
||||
|
@ -398,14 +412,24 @@ public class Plugin implements ServerEventListener {
|
|||
daemonTimer = null;
|
||||
|
||||
//stop file-watchers
|
||||
if (regionFileWatchServices != null) {
|
||||
regionFileWatchServices.values().forEach(RegionFileWatchService::close);
|
||||
regionFileWatchServices.clear();
|
||||
if (mapUpdateServices != null) {
|
||||
mapUpdateServices.values().forEach(MapUpdateService::close);
|
||||
mapUpdateServices.clear();
|
||||
}
|
||||
regionFileWatchServices = null;
|
||||
mapUpdateServices = null;
|
||||
|
||||
//stop services
|
||||
// stop render-manager
|
||||
if (renderManager != null){
|
||||
if (renderManager.getCurrentRenderTask() != null) {
|
||||
renderManager.removeAllRenderTasks();
|
||||
if (!renderManager.isRunning()) renderManager.start(1);
|
||||
try {
|
||||
renderManager.awaitIdle(true);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
renderManager.stop();
|
||||
try {
|
||||
renderManager.awaitShutdown();
|
||||
|
@ -413,8 +437,11 @@ public class Plugin implements ServerEventListener {
|
|||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
renderManager = null;
|
||||
|
||||
//save
|
||||
save();
|
||||
|
||||
// stop webserver
|
||||
if (webServer != null && !keepWebserver) {
|
||||
try {
|
||||
webServer.close();
|
||||
|
@ -424,7 +451,7 @@ public class Plugin implements ServerEventListener {
|
|||
webServer = null;
|
||||
}
|
||||
|
||||
if (webLogger != null) {
|
||||
if (webLogger != null && !keepWebserver) {
|
||||
try {
|
||||
webLogger.close();
|
||||
} catch (Exception ex) {
|
||||
|
@ -447,9 +474,6 @@ public class Plugin implements ServerEventListener {
|
|||
Logger.global.remove(DEBUG_FILE_LOG_NAME);
|
||||
|
||||
//clear resources
|
||||
worlds = null;
|
||||
maps = null;
|
||||
|
||||
pluginState = null;
|
||||
|
||||
//done
|
||||
|
@ -479,7 +503,7 @@ public class Plugin implements ServerEventListener {
|
|||
}
|
||||
|
||||
// hold and reuse loaded resourcepack
|
||||
ResourcePack preloadedResourcePack = this.blueMap.getResourcePackIfLoaded().orElse(null);
|
||||
ResourcePack preloadedResourcePack = this.blueMap.getResourcePack();
|
||||
|
||||
unload();
|
||||
load(preloadedResourcePack);
|
||||
|
@ -491,10 +515,12 @@ public class Plugin implements ServerEventListener {
|
|||
}
|
||||
|
||||
public synchronized void save() {
|
||||
if (blueMap == null) return;
|
||||
|
||||
if (pluginState != null) {
|
||||
try {
|
||||
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
|
||||
.path(blueMap.getConfigs().getCoreConfig().getData().resolve("pluginState.json"))
|
||||
.path(blueMap.getConfig().getCoreConfig().getData().resolve("pluginState.json"))
|
||||
.build();
|
||||
loader.save(loader.createNode().set(PluginState.class, pluginState));
|
||||
} catch (IOException ex) {
|
||||
|
@ -502,38 +528,41 @@ public class Plugin implements ServerEventListener {
|
|||
}
|
||||
}
|
||||
|
||||
if (maps != null) {
|
||||
for (BmMap map : maps.values()) {
|
||||
map.save();
|
||||
}
|
||||
var maps = blueMap.getMaps();
|
||||
for (BmMap map : maps.values()) {
|
||||
map.save();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveMarkerStates() {
|
||||
if (maps != null) {
|
||||
for (BmMap map : maps.values()) {
|
||||
map.saveMarkerState();
|
||||
}
|
||||
if (blueMap == null) return;
|
||||
|
||||
var maps = blueMap.getMaps();
|
||||
for (BmMap map : maps.values()) {
|
||||
map.saveMarkerState();
|
||||
}
|
||||
}
|
||||
|
||||
public void savePlayerStates() {
|
||||
if (maps != null) {
|
||||
for (BmMap map : maps.values()) {
|
||||
var dataSupplier = new LivePlayersDataSupplier(
|
||||
serverInterface,
|
||||
getConfigs().getPluginConfig(),
|
||||
map.getWorldId(),
|
||||
Predicate.not(pluginState::isPlayerHidden)
|
||||
);
|
||||
try (
|
||||
OutputStream out = map.getStorage().writeMeta(map.getId(), BmMap.META_FILE_PLAYERS);
|
||||
Writer writer = new OutputStreamWriter(out)
|
||||
) {
|
||||
writer.write(dataSupplier.get());
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logError("Failed to save players for map '" + map.getId() + "'!", ex);
|
||||
}
|
||||
if (blueMap == null) return;
|
||||
|
||||
var maps = blueMap.getMaps();
|
||||
for (BmMap map : maps.values()) {
|
||||
var serverWorld = serverInterface.getServerWorld(map.getWorld()).orElse(null);
|
||||
if (serverWorld == null) continue;
|
||||
var dataSupplier = new LivePlayersDataSupplier(
|
||||
serverInterface,
|
||||
getBlueMap().getConfig().getPluginConfig(),
|
||||
serverWorld,
|
||||
Predicate.not(pluginState::isPlayerHidden)
|
||||
);
|
||||
try (
|
||||
OutputStream out = map.getStorage().players().write();
|
||||
Writer writer = new OutputStreamWriter(out)
|
||||
) {
|
||||
writer.write(dataSupplier.get());
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logError("Failed to save players for map '" + map.getId() + "'!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -542,23 +571,27 @@ public class Plugin implements ServerEventListener {
|
|||
stopWatchingMap(map);
|
||||
|
||||
try {
|
||||
RegionFileWatchService watcher = new RegionFileWatchService(renderManager, map, false);
|
||||
MapUpdateService watcher = new MapUpdateService(renderManager, map);
|
||||
watcher.start();
|
||||
regionFileWatchServices.put(map.getId(), watcher);
|
||||
mapUpdateServices.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);
|
||||
Logger.global.logError("Failed to create update-watcher for map: " + map.getId() +
|
||||
" (This means the map might not automatically update)", ex);
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
Logger.global.logWarning("Update-watcher for map '" + map.getId() + "' is not supported for the world-type." +
|
||||
" (This means the map might not automatically update)");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stopWatchingMap(BmMap map) {
|
||||
RegionFileWatchService watcher = regionFileWatchServices.remove(map.getId());
|
||||
MapUpdateService watcher = mapUpdateServices.remove(map.getId());
|
||||
if (watcher != null) {
|
||||
watcher.close();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean flushWorldUpdates(World world) throws IOException {
|
||||
var implWorld = serverInterface.getWorld(world.getSaveFolder()).orElse(null);
|
||||
var implWorld = serverInterface.getServerWorld(world).orElse(null);
|
||||
if (implWorld != null) return implWorld.persistWorldChanges();
|
||||
return false;
|
||||
}
|
||||
|
@ -588,8 +621,8 @@ public class Plugin implements ServerEventListener {
|
|||
}
|
||||
|
||||
public boolean checkPausedByPlayerCount() {
|
||||
CoreConfig coreConfig = getConfigs().getCoreConfig();
|
||||
PluginConfig pluginConfig = getConfigs().getPluginConfig();
|
||||
CoreConfig coreConfig = getBlueMap().getConfig().getCoreConfig();
|
||||
PluginConfig pluginConfig = getBlueMap().getConfig().getPluginConfig();
|
||||
|
||||
if (
|
||||
pluginConfig.getPlayerRenderLimit() > 0 &&
|
||||
|
@ -604,54 +637,18 @@ public class Plugin implements ServerEventListener {
|
|||
}
|
||||
}
|
||||
|
||||
public ServerInterface getServerInterface() {
|
||||
return serverInterface;
|
||||
}
|
||||
|
||||
public BlueMapService getBlueMap() {
|
||||
return blueMap;
|
||||
}
|
||||
|
||||
public BlueMapConfigProvider getConfigs() {
|
||||
return blueMap.getConfigs();
|
||||
}
|
||||
|
||||
public PluginState getPluginState() {
|
||||
return pluginState;
|
||||
}
|
||||
|
||||
public Map<String, World> getWorlds(){
|
||||
return worlds;
|
||||
}
|
||||
|
||||
public Map<String, BmMap> getMaps(){
|
||||
return maps;
|
||||
}
|
||||
|
||||
public RenderManager getRenderManager() {
|
||||
return renderManager;
|
||||
}
|
||||
|
||||
public HttpServer getWebServer() {
|
||||
return webServer;
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public String getImplementationType() {
|
||||
return implementationType;
|
||||
}
|
||||
|
||||
public PlayerSkinUpdater getSkinUpdater() {
|
||||
return skinUpdater;
|
||||
public @Nullable World getWorld(ServerWorld serverWorld) {
|
||||
String id = World.id(serverWorld.getWorldFolder(), serverWorld.getDimension());
|
||||
return getBlueMap().getWorlds().get(id);
|
||||
}
|
||||
|
||||
private void initFileWatcherTasks() {
|
||||
for (BmMap map : maps.values()) {
|
||||
if (pluginState.getMapState(map).isUpdateEnabled()) {
|
||||
startWatchingMap(map);
|
||||
var maps = blueMap.getMaps();
|
||||
if (maps != null) {
|
||||
for (BmMap map : maps.values()) {
|
||||
if (pluginState.getMapState(map).isUpdateEnabled()) {
|
||||
startWatchingMap(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,151 +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 com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
||||
import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
|
||||
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");
|
||||
FileHelper.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 + " (Map: " + map.getId() + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -30,13 +30,13 @@ import de.bluecolored.bluemap.common.plugin.text.TextColor;
|
|||
import de.bluecolored.bluemap.common.plugin.text.TextFormat;
|
||||
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
||||
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import org.apache.commons.lang3.time.DurationFormatUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
public class CommandHelper {
|
||||
|
@ -97,7 +97,13 @@ public class CommandHelper {
|
|||
|
||||
long etaMs = renderer.estimateCurrentRenderTaskTimeRemaining();
|
||||
if (etaMs > 0) {
|
||||
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0ETA: ", TextColor.WHITE, DurationFormatUtils.formatDuration(etaMs, "HH:mm:ss")));
|
||||
Duration eta = Duration.of(etaMs, ChronoUnit.MILLIS);
|
||||
String etaString = "%d:%02d:%02d".formatted(
|
||||
eta.toHours(),
|
||||
eta.toMinutesPart(),
|
||||
eta.toSecondsPart()
|
||||
);
|
||||
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0ETA: ", TextColor.WHITE, etaString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +112,7 @@ public class CommandHelper {
|
|||
if (plugin.checkPausedByPlayerCount()) {
|
||||
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
|
||||
Text.of(TextColor.GOLD, "paused")));
|
||||
lines.add(Text.of(TextColor.GRAY, TextFormat.ITALIC, "\u00A0\u00A0\u00A0(there are " + plugin.getConfigs().getPluginConfig().getPlayerRenderLimit() + " or more players online)"));
|
||||
lines.add(Text.of(TextColor.GRAY, TextFormat.ITALIC, "\u00A0\u00A0\u00A0(there are " + plugin.getBlueMap().getConfig().getPluginConfig().getPlayerRenderLimit() + " or more players online)"));
|
||||
} else {
|
||||
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
|
||||
Text.of(TextColor.RED, "stopped")
|
||||
|
@ -134,20 +140,22 @@ public class CommandHelper {
|
|||
|
||||
public Text worldHelperHover() {
|
||||
StringJoiner joiner = new StringJoiner("\n");
|
||||
for (World world : plugin.getWorlds().values()) {
|
||||
joiner.add(world.getName());
|
||||
for (String worldId : plugin.getBlueMap().getWorlds().keySet()) {
|
||||
joiner.add(worldId);
|
||||
}
|
||||
|
||||
return Text.of("world").setHoverText(Text.of(TextColor.WHITE, "Available worlds: \n", TextColor.GRAY, joiner.toString()));
|
||||
return Text.of(TextFormat.UNDERLINED, "world")
|
||||
.setHoverText(Text.of(TextColor.WHITE, "Available worlds: \n", TextColor.GRAY, joiner.toString()));
|
||||
}
|
||||
|
||||
public Text mapHelperHover() {
|
||||
StringJoiner joiner = new StringJoiner("\n");
|
||||
for (String mapId : plugin.getMaps().keySet()) {
|
||||
for (String mapId : plugin.getBlueMap().getMaps().keySet()) {
|
||||
joiner.add(mapId);
|
||||
}
|
||||
|
||||
return Text.of("map").setHoverText(Text.of(TextColor.WHITE, "Available maps: \n", TextColor.GRAY, joiner.toString()));
|
||||
return Text.of(TextFormat.UNDERLINED, "map")
|
||||
.setHoverText(Text.of(TextColor.WHITE, "Available maps: \n", TextColor.GRAY, joiner.toString()));
|
||||
}
|
||||
|
||||
public synchronized Optional<RenderTask> getTaskForRef(String ref) {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.plugin.commands;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2d;
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
|
@ -47,25 +48,29 @@ import de.bluecolored.bluemap.common.plugin.text.TextFormat;
|
|||
import de.bluecolored.bluemap.common.rendermanager.*;
|
||||
import de.bluecolored.bluemap.common.serverinterface.CommandSource;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.debug.StateDumper;
|
||||
import de.bluecolored.bluemap.common.debug.StateDumper;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.map.MapRenderState;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.TileInfoRegion;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.TileState;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.world.Block;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import de.bluecolored.bluemap.core.world.block.Block;
|
||||
import de.bluecolored.bluemap.core.world.block.entity.BlockEntity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Commands<S> {
|
||||
|
||||
public static final String DEFAULT_MARKER_SET_ID = "markers";
|
||||
|
||||
private final Plugin plugin;
|
||||
private final CommandDispatcher<S> dispatcher;
|
||||
private final Function<S, CommandSource> commandSourceInterface;
|
||||
|
@ -121,6 +126,15 @@ public class Commands<S> {
|
|||
.then(argument("z", DoubleArgumentType.doubleArg())
|
||||
.executes(this::debugBlockCommand))))))
|
||||
|
||||
.then(literal("map")
|
||||
.requires(requirements("bluemap.debug"))
|
||||
.then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin))
|
||||
.executes(this::debugMapCommand)
|
||||
|
||||
.then(argument("x", IntegerArgumentType.integer())
|
||||
.then(argument("z", IntegerArgumentType.integer())
|
||||
.executes(this::debugMapCommand)))))
|
||||
|
||||
.then(literal("flush")
|
||||
.requires(requirements("bluemap.debug"))
|
||||
.executes(this::debugFlushCommand)
|
||||
|
@ -167,6 +181,13 @@ public class Commands<S> {
|
|||
this::forceUpdateCommand
|
||||
).build();
|
||||
|
||||
LiteralCommandNode<S> fixEdgesCommand =
|
||||
addRenderArguments(
|
||||
literal("fix-edges")
|
||||
.requires(requirements("bluemap.update.force")),
|
||||
this::fixEdgesCommand
|
||||
).build();
|
||||
|
||||
LiteralCommandNode<S> updateCommand =
|
||||
addRenderArguments(
|
||||
literal("update")
|
||||
|
@ -222,6 +243,7 @@ public class Commands<S> {
|
|||
baseCommand.addChild(freezeCommand);
|
||||
baseCommand.addChild(unfreezeCommand);
|
||||
baseCommand.addChild(forceUpdateCommand);
|
||||
baseCommand.addChild(fixEdgesCommand);
|
||||
baseCommand.addChild(updateCommand);
|
||||
baseCommand.addChild(cancelCommand);
|
||||
baseCommand.addChild(purgeCommand);
|
||||
|
@ -281,10 +303,10 @@ public class Commands<S> {
|
|||
}
|
||||
}
|
||||
|
||||
private Optional<World> parseWorld(String worldName) {
|
||||
for (World world : plugin.getWorlds().values()) {
|
||||
if (world.getName().equalsIgnoreCase(worldName)) {
|
||||
return Optional.of(world);
|
||||
private Optional<World> parseWorld(String worldId) {
|
||||
for (var entry : plugin.getBlueMap().getWorlds().entrySet()) {
|
||||
if (entry.getKey().equals(worldId)) {
|
||||
return Optional.of(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,8 +314,8 @@ public class Commands<S> {
|
|||
}
|
||||
|
||||
private Optional<BmMap> parseMap(String mapId) {
|
||||
for (BmMap map : plugin.getMaps().values()) {
|
||||
if (map.getId().equalsIgnoreCase(mapId)) {
|
||||
for (BmMap map : plugin.getBlueMap().getMaps().values()) {
|
||||
if (map.getId().equals(mapId)) {
|
||||
return Optional.of(map);
|
||||
}
|
||||
}
|
||||
|
@ -327,32 +349,27 @@ public class Commands<S> {
|
|||
renderThreadCount = plugin.getRenderManager().getWorkerThreadCount();
|
||||
}
|
||||
|
||||
MinecraftVersion minecraftVersion = plugin.getServerInterface().getMinecraftVersion();
|
||||
String minecraftVersion = plugin.getServerInterface().getMinecraftVersion();
|
||||
|
||||
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));
|
||||
source.sendMessage(Text.of(TextColor.GRAY, "Implementation: ", TextColor.WHITE, plugin.getImplementationType()));
|
||||
source.sendMessage(Text.of(
|
||||
TextColor.GRAY, "Minecraft compatibility: ", TextColor.WHITE, minecraftVersion.getVersionString(),
|
||||
TextColor.GRAY, " (" + minecraftVersion.getResource().getVersion().getVersionString() + ")"
|
||||
));
|
||||
source.sendMessage(Text.of(TextColor.GRAY, "Minecraft: ", TextColor.WHITE, minecraftVersion));
|
||||
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 (minecraftVersion.isAtLeast(new MinecraftVersion(1, 15))) {
|
||||
String clipboardValue =
|
||||
"Version: " + BlueMap.VERSION + "\n" +
|
||||
"Commit: " + BlueMap.GIT_HASH + "\n" +
|
||||
"Implementation: " + plugin.getImplementationType() + "\n" +
|
||||
"Minecraft compatibility: " + minecraftVersion.getVersionString() + " (" + minecraftVersion.getResource().getVersion().getVersionString() + ")\n" +
|
||||
"Render-threads: " + renderThreadCount + "\n" +
|
||||
"Available processors: " + Runtime.getRuntime().availableProcessors() + "\n" +
|
||||
"Available memory: " + Runtime.getRuntime().maxMemory() / 1024L / 1024L + " MiB";
|
||||
source.sendMessage(Text.of(TextColor.DARK_GRAY, "[copy to clipboard]")
|
||||
.setClickAction(Text.ClickAction.COPY_TO_CLIPBOARD, clipboardValue)
|
||||
.setHoverText(Text.of(TextColor.GRAY, "click to copy the above text .. ", TextFormat.ITALIC, TextColor.GRAY, "duh!")));
|
||||
}
|
||||
String clipboardValue =
|
||||
"Version: " + BlueMap.VERSION + "\n" +
|
||||
"Commit: " + BlueMap.GIT_HASH + "\n" +
|
||||
"Implementation: " + plugin.getImplementationType() + "\n" +
|
||||
"Minecraft: " + minecraftVersion + "\n" +
|
||||
"Render-threads: " + renderThreadCount + "\n" +
|
||||
"Available processors: " + Runtime.getRuntime().availableProcessors() + "\n" +
|
||||
"Available memory: " + Runtime.getRuntime().maxMemory() / 1024L / 1024L + " MiB";
|
||||
source.sendMessage(Text.of(TextColor.DARK_GRAY, "[copy to clipboard]")
|
||||
.setClickAction(Text.ClickAction.COPY_TO_CLIPBOARD, clipboardValue)
|
||||
.setHoverText(Text.of(TextColor.GRAY, "click to copy the above text .. ", TextFormat.ITALIC, TextColor.GRAY, "duh!")));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -416,7 +433,7 @@ public class Commands<S> {
|
|||
public int debugClearCacheCommand(CommandContext<S> context) {
|
||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
for (World world : plugin.getWorlds().values()) {
|
||||
for (World world : plugin.getBlueMap().getWorlds().values()) {
|
||||
world.invalidateChunkCache();
|
||||
}
|
||||
|
||||
|
@ -436,7 +453,7 @@ public class Commands<S> {
|
|||
world = parseWorld(worldName.get()).orElse(null);
|
||||
|
||||
if (world == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this name: ", TextColor.WHITE, worldName.get()));
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this id: ", TextColor.WHITE, worldName.get()));
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
|
@ -465,6 +482,86 @@ public class Commands<S> {
|
|||
return 1;
|
||||
}
|
||||
|
||||
public int debugMapCommand(CommandContext<S> context) {
|
||||
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
// parse arguments
|
||||
String mapId = context.getArgument("map", String.class);
|
||||
Optional<Integer> x = getOptionalArgument(context, "x", Integer.class);
|
||||
Optional<Integer> z = getOptionalArgument(context, "z", Integer.class);
|
||||
|
||||
final BmMap map = parseMap(mapId).orElse(null);
|
||||
if (map == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this id: ", TextColor.WHITE, mapId));
|
||||
return 0;
|
||||
}
|
||||
|
||||
final Vector2i position;
|
||||
if (x.isPresent() && z.isPresent()) {
|
||||
position = new Vector2i(x.get(), z.get());
|
||||
} else {
|
||||
position = source.getPosition()
|
||||
.map(v -> v.toVector2(true))
|
||||
.map(Vector2d::floor)
|
||||
.map(Vector2d::toInt)
|
||||
.orElse(null);
|
||||
|
||||
if (position == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "Can't detect a location from this command-source, you'll have to define a position!"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
// collect and output debug info
|
||||
Grid chunkGrid = map.getWorld().getChunkGrid();
|
||||
Grid regionGrid = map.getWorld().getRegionGrid();
|
||||
Grid tileGrid = map.getHiresModelManager().getTileGrid();
|
||||
|
||||
Vector2i regionPos = regionGrid.getCell(position);
|
||||
Vector2i chunkPos = chunkGrid.getCell(position);
|
||||
Vector2i tilePos = tileGrid.getCell(position);
|
||||
|
||||
TileInfoRegion.TileInfo tileInfo = map.getMapTileState().get(tilePos.getX(), tilePos.getY());
|
||||
|
||||
int lastChunkHash = map.getMapChunkState().get(chunkPos.getX(), chunkPos.getY());
|
||||
int currentChunkHash = 0;
|
||||
|
||||
class FindHashConsumer implements ChunkConsumer.ListOnly {
|
||||
public int timestamp = 0;
|
||||
|
||||
@Override
|
||||
public void accept(int chunkX, int chunkZ, int timestamp) {
|
||||
if (chunkPos.getX() == chunkX && chunkPos.getY() == chunkZ)
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
FindHashConsumer findHashConsumer = new FindHashConsumer();
|
||||
map.getWorld().getRegion(regionPos.getX(), regionPos.getY())
|
||||
.iterateAllChunks(findHashConsumer);
|
||||
currentChunkHash = findHashConsumer.timestamp;
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to load chunk-hash.", e);
|
||||
}
|
||||
|
||||
Map<String, Object> lines = new LinkedHashMap<>();
|
||||
lines.put("region-pos", regionPos);
|
||||
lines.put("chunk-pos", chunkPos);
|
||||
lines.put("chunk-curr-hash", currentChunkHash);
|
||||
lines.put("chunk-last-hash", lastChunkHash);
|
||||
lines.put("tile-pos", tilePos);
|
||||
lines.put("tile-render-time", tileInfo.getRenderTime());
|
||||
lines.put("tile-state", tileInfo.getState().getKey().getFormatted());
|
||||
|
||||
source.sendMessage(Text.of(TextColor.GOLD, "Map tile info:"));
|
||||
source.sendMessage(formatMap(lines));
|
||||
}, "BlueMap-Plugin-DebugMapCommand").start();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int debugBlockCommand(CommandContext<S> context) {
|
||||
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
|
@ -482,7 +579,7 @@ public class Commands<S> {
|
|||
position = new Vector3d(x.get(), y.get(), z.get());
|
||||
|
||||
if (world == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this name: ", TextColor.WHITE, worldName.get()));
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " with this id: ", TextColor.WHITE, worldName.get()));
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
|
@ -499,31 +596,55 @@ public class Commands<S> {
|
|||
// collect and output debug info
|
||||
Vector3i blockPos = position.floor().toInt();
|
||||
Block<?> block = new Block<>(world, blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
Block<?> blockBelow = new Block<>(null, 0, 0, 0).copy(block, 0, -1, 0);
|
||||
|
||||
// populate lazy-loaded values
|
||||
block.getBlockState();
|
||||
block.getBiomeId();
|
||||
block.getLightData();
|
||||
|
||||
blockBelow.getBlockState();
|
||||
blockBelow.getBiomeId();
|
||||
blockBelow.getLightData();
|
||||
Block<?> blockBelow = new Block<>(world, blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
|
||||
|
||||
source.sendMessages(Arrays.asList(
|
||||
Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block),
|
||||
Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow)
|
||||
Text.of(TextColor.GOLD, "Block at you: \n", formatBlock(block)),
|
||||
Text.of(TextColor.GOLD, "Block below you: \n", formatBlock(blockBelow))
|
||||
));
|
||||
}, "BlueMap-Plugin-DebugBlockCommand").start();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private Text formatBlock(Block<?> block) {
|
||||
World world = block.getWorld();
|
||||
Chunk chunk = block.getChunk();
|
||||
|
||||
Map<String, Object> lines = new LinkedHashMap<>();
|
||||
lines.put("world-id", world.getId());
|
||||
lines.put("world-name", world.getName());
|
||||
lines.put("chunk-is-generated", chunk.isGenerated());
|
||||
lines.put("chunk-has-lightdata", chunk.hasLightData());
|
||||
lines.put("chunk-inhabited-time", chunk.getInhabitedTime());
|
||||
lines.put("block-state", block.getBlockState());
|
||||
lines.put("biome", block.getBiome().getKey());
|
||||
lines.put("position", block.getX() + " | " + block.getY() + " | " + block.getZ());
|
||||
lines.put("block-light", block.getBlockLightLevel());
|
||||
lines.put("sun-light", block.getSunLightLevel());
|
||||
|
||||
BlockEntity blockEntity = block.getBlockEntity();
|
||||
if (blockEntity != null) {
|
||||
lines.put("block-entity", blockEntity);
|
||||
}
|
||||
|
||||
return formatMap(lines);
|
||||
}
|
||||
|
||||
private Text formatMap(Map<String, Object> lines) {
|
||||
Object[] textElements = lines.entrySet().stream()
|
||||
.flatMap(e -> Stream.of(TextColor.GRAY, e.getKey(), ": ", TextColor.WHITE, e.getValue(), "\n"))
|
||||
.toArray(Object[]::new);
|
||||
textElements[textElements.length - 1] = "";
|
||||
|
||||
return Text.of(textElements);
|
||||
}
|
||||
|
||||
public int debugDumpCommand(CommandContext<S> context) {
|
||||
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
try {
|
||||
Path file = plugin.getConfigs().getCoreConfig().getData().resolve("dump.json");
|
||||
Path file = plugin.getBlueMap().getConfig().getCoreConfig().getData().resolve("dump.json.gz");
|
||||
StateDumper.global().dump(file);
|
||||
|
||||
source.sendMessage(Text.of(TextColor.GREEN, "Dump created at: " + file));
|
||||
|
@ -562,7 +683,7 @@ public class Commands<S> {
|
|||
new Thread(() -> {
|
||||
plugin.getPluginState().setRenderThreadsEnabled(true);
|
||||
|
||||
plugin.getRenderManager().start(plugin.getConfigs().getCoreConfig().resolveRenderThreadCount());
|
||||
plugin.getRenderManager().start(plugin.getBlueMap().getConfig().getCoreConfig().resolveRenderThreadCount());
|
||||
source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads started!"));
|
||||
|
||||
plugin.save();
|
||||
|
@ -583,7 +704,7 @@ public class Commands<S> {
|
|||
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));
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this id: ", TextColor.WHITE, mapString));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -624,7 +745,7 @@ public class Commands<S> {
|
|||
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));
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this id: ", TextColor.WHITE, mapString));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -649,14 +770,18 @@ public class Commands<S> {
|
|||
}
|
||||
|
||||
public int forceUpdateCommand(CommandContext<S> context) {
|
||||
return updateCommand(context, true);
|
||||
return updateCommand(context, s -> true);
|
||||
}
|
||||
|
||||
public int fixEdgesCommand(CommandContext<S> context) {
|
||||
return updateCommand(context, s -> s == TileState.RENDERED_EDGE);
|
||||
}
|
||||
|
||||
public int updateCommand(CommandContext<S> context) {
|
||||
return updateCommand(context, false);
|
||||
return updateCommand(context, s -> false);
|
||||
}
|
||||
|
||||
public int updateCommand(CommandContext<S> context, boolean force) {
|
||||
public int updateCommand(CommandContext<S> context, Predicate<TileState> force) {
|
||||
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
// parse world/map argument
|
||||
|
@ -671,7 +796,8 @@ public class Commands<S> {
|
|||
mapToRender = parseMap(worldOrMap.get()).orElse(null);
|
||||
|
||||
if (mapToRender == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, worldOrMap.get()));
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ",
|
||||
helper.mapHelperHover(), " with this id: ", TextColor.WHITE, worldOrMap.get()));
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
|
@ -682,7 +808,7 @@ public class Commands<S> {
|
|||
mapToRender = null;
|
||||
|
||||
if (worldToRender == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to update!").setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " <world|map>")));
|
||||
source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to update!"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -699,7 +825,7 @@ public class Commands<S> {
|
|||
} 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 update with a radius!").setHoverText(Text.of(TextColor.GRAY, "/bluemap " + (force ? "force-update" : "update") + " <x> <z> " + radius)));
|
||||
source.sendMessage(Text.of(TextColor.RED, "Can't detect a position from this command-source, you'll have to define x,z coordinates to update with a radius!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -714,16 +840,12 @@ public class Commands<S> {
|
|||
try {
|
||||
List<BmMap> maps = new ArrayList<>();
|
||||
if (worldToRender != null) {
|
||||
var world = plugin.getServerInterface().getWorld(worldToRender.getSaveFolder()).orElse(null);
|
||||
if (world != null) world.persistWorldChanges();
|
||||
|
||||
for (BmMap map : plugin.getMaps().values()) {
|
||||
if (map.getWorld().getSaveFolder().equals(worldToRender.getSaveFolder())) maps.add(map);
|
||||
plugin.flushWorldUpdates(worldToRender);
|
||||
for (BmMap map : plugin.getBlueMap().getMaps().values()) {
|
||||
if (map.getWorld().equals(worldToRender)) maps.add(map);
|
||||
}
|
||||
} else {
|
||||
var world = plugin.getServerInterface().getWorld(mapToRender.getWorld().getSaveFolder()).orElse(null);
|
||||
if (world != null) world.persistWorldChanges();
|
||||
|
||||
plugin.flushWorldUpdates(mapToRender.getWorld());
|
||||
maps.add(mapToRender);
|
||||
}
|
||||
|
||||
|
@ -733,15 +855,11 @@ public class Commands<S> {
|
|||
}
|
||||
|
||||
for (BmMap map : maps) {
|
||||
MapUpdateTask updateTask = new MapUpdateTask(map, center, radius);
|
||||
MapUpdateTask updateTask = new MapUpdateTask(map, center, radius, force);
|
||||
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, "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."));
|
||||
|
||||
|
@ -790,7 +908,7 @@ public class Commands<S> {
|
|||
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));
|
||||
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this id: ", TextColor.WHITE, mapString));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -831,8 +949,8 @@ public class Commands<S> {
|
|||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
source.sendMessage(Text.of(TextColor.BLUE, "Worlds loaded by BlueMap:"));
|
||||
for (var entry : plugin.getWorlds().entrySet()) {
|
||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getValue().getName()).setHoverText(Text.of(entry.getValue().getSaveFolder(), TextColor.GRAY, " (" + entry.getKey() + ")")));
|
||||
for (var entry : plugin.getBlueMap().getWorlds().entrySet()) {
|
||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getKey()));
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
@ -842,7 +960,7 @@ public class Commands<S> {
|
|||
List<Text> lines = new ArrayList<>();
|
||||
lines.add(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:"));
|
||||
|
||||
for (BmMap map : plugin.getMaps().values()) {
|
||||
for (BmMap map : plugin.getBlueMap().getMaps().values()) {
|
||||
boolean frozen = !plugin.getPluginState().getMapState(map).isUpdateEnabled();
|
||||
|
||||
lines.add(Text.of(TextColor.GRAY, " - ",
|
||||
|
@ -850,12 +968,12 @@ public class Commands<S> {
|
|||
TextColor.GRAY, " (" + map.getName() + ")"));
|
||||
|
||||
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0World: ",
|
||||
TextColor.DARK_GRAY, map.getWorld().getName()));
|
||||
TextColor.DARK_GRAY, map.getWorld().getId()));
|
||||
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Last Update: ",
|
||||
TextColor.DARK_GRAY, helper.formatTime(map.getRenderState().getLatestRenderTime())));
|
||||
TextColor.DARK_GRAY, helper.formatTime(map.getMapTileState().getLastRenderTime() * 1000L)));
|
||||
|
||||
if (frozen)
|
||||
lines.add(Text.of(TextColor.AQUA, TextFormat.ITALIC, "This map is frozen!"));
|
||||
lines.add(Text.of(TextColor.AQUA, TextFormat.ITALIC, "\u00A0\u00A0\u00A0This map is frozen!"));
|
||||
}
|
||||
|
||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
@ -868,9 +986,14 @@ public class Commands<S> {
|
|||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
source.sendMessage(Text.of(TextColor.BLUE, "Storages loaded by BlueMap:"));
|
||||
for (var entry : plugin.getBlueMap().getConfigs().getStorageConfigs().entrySet()) {
|
||||
for (var entry : plugin.getBlueMap().getConfig().getStorageConfigs().entrySet()) {
|
||||
String storageTypeKey = "?";
|
||||
try {
|
||||
storageTypeKey = entry.getValue().getStorageType().getKey().getFormatted();
|
||||
} catch (ConfigurationException ignore) {} // should never happen
|
||||
|
||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getKey())
|
||||
.setHoverText(Text.of(entry.getValue().getStorageType().name()))
|
||||
.setHoverText(Text.of(storageTypeKey))
|
||||
.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap storages " + entry.getKey())
|
||||
);
|
||||
}
|
||||
|
@ -884,18 +1007,19 @@ public class Commands<S> {
|
|||
|
||||
Storage storage;
|
||||
try {
|
||||
storage = plugin.getBlueMap().getStorage(storageId);
|
||||
} catch (ConfigurationException ex) {
|
||||
source.sendMessage(Text.of(TextColor.RED, ex.getMessage()));
|
||||
storage = plugin.getBlueMap().getOrLoadStorage(storageId);
|
||||
} catch (ConfigurationException | InterruptedException ex) {
|
||||
Logger.global.logError("Unexpected exception trying to load storage '" + storageId + "'!", ex);
|
||||
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to load this storage. Please check the console for more details..."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
Collection<String> mapIds;
|
||||
try {
|
||||
mapIds = storage.collectMapIds();
|
||||
mapIds = storage.mapIds().toList();
|
||||
} catch (IOException ex) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to access this storage. Please check the console for more details..."));
|
||||
Logger.global.logError("Unexpected exception trying to load mapIds from storage '" + storageId + "'!", ex);
|
||||
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to access this storage. Please check the console for more details..."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -904,8 +1028,8 @@ public class Commands<S> {
|
|||
source.sendMessage(Text.of(TextColor.GRAY, " <empty storage>"));
|
||||
} else {
|
||||
for (String mapId : mapIds) {
|
||||
BmMap map = plugin.getMaps().get(mapId);
|
||||
boolean isLoaded = map != null && map.getStorage().equals(storage);
|
||||
BmMap map = plugin.getBlueMap().getMaps().get(mapId);
|
||||
boolean isLoaded = map != null && map.getStorage().equals(storage.map(mapId));
|
||||
|
||||
if (isLoaded) {
|
||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, mapId, TextColor.GREEN, TextFormat.ITALIC, " (loaded)"));
|
||||
|
@ -923,15 +1047,16 @@ public class Commands<S> {
|
|||
String storageId = context.getArgument("storage", String.class);
|
||||
String mapId = context.getArgument("map", String.class);
|
||||
|
||||
Storage storage;
|
||||
MapStorage storage;
|
||||
try {
|
||||
storage = plugin.getBlueMap().getStorage(storageId);
|
||||
} catch (ConfigurationException ex) {
|
||||
source.sendMessage(Text.of(TextColor.RED, ex.getMessage()));
|
||||
storage = plugin.getBlueMap().getOrLoadStorage(storageId).map(mapId);
|
||||
} catch (ConfigurationException | InterruptedException ex) {
|
||||
Logger.global.logError("Unexpected exception trying to load storage '" + storageId + "'!", ex);
|
||||
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to load this storage. Please check the console for more details..."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
BmMap map = plugin.getMaps().get(mapId);
|
||||
BmMap map = plugin.getBlueMap().getMaps().get(mapId);
|
||||
boolean isLoaded = map != null && map.getStorage().equals(storage);
|
||||
if (isLoaded) {
|
||||
Text purgeCommand = Text.of(TextColor.WHITE, "/bluemap purge " + mapId)
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
package de.bluecolored.bluemap.common.plugin.commands;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
@ -40,7 +39,7 @@ public class MapSuggestionProvider<S> extends AbstractSuggestionProvider<S> {
|
|||
|
||||
@Override
|
||||
public Collection<String> getPossibleValues() {
|
||||
return new HashSet<>(plugin.getMaps().keySet());
|
||||
return new HashSet<>(plugin.getBlueMap().getMaps().keySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class StorageSuggestionProvider<S> extends AbstractSuggestionProvider<S>
|
|||
|
||||
@Override
|
||||
public Collection<String> getPossibleValues() {
|
||||
return plugin.getBlueMap().getConfigs().getStorageConfigs().keySet();
|
||||
return plugin.getBlueMap().getConfig().getStorageConfigs().keySet();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
package de.bluecolored.bluemap.common.plugin.commands;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
@ -41,13 +40,8 @@ public class WorldOrMapSuggestionProvider<S> extends AbstractSuggestionProvider<
|
|||
@Override
|
||||
public Collection<String> getPossibleValues() {
|
||||
Collection<String> values = new HashSet<>();
|
||||
|
||||
for (World world : plugin.getWorlds().values()) {
|
||||
values.add(world.getName());
|
||||
}
|
||||
|
||||
values.addAll(plugin.getMaps().keySet());
|
||||
|
||||
values.addAll(plugin.getBlueMap().getWorlds().keySet());
|
||||
values.addAll(plugin.getBlueMap().getMaps().keySet());
|
||||
return values;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
package de.bluecolored.bluemap.common.plugin.commands;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
@ -40,13 +39,7 @@ public class WorldSuggestionProvider<S> extends AbstractSuggestionProvider<S> {
|
|||
|
||||
@Override
|
||||
public Collection<String> getPossibleValues() {
|
||||
Collection<String> values = new HashSet<>();
|
||||
|
||||
for (World world : plugin.getWorlds().values()) {
|
||||
values.add(world.getName());
|
||||
}
|
||||
|
||||
return values;
|
||||
return new HashSet<>(plugin.getBlueMap().getWorlds().keySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.plugin.skins;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.api.plugin.PlayerIconFactory;
|
||||
import de.bluecolored.bluemap.api.plugin.SkinProvider;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
|
@ -45,7 +44,6 @@ import java.util.concurrent.CompletionException;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@DebugDump
|
||||
public class PlayerSkinUpdater implements ServerEventListener {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
@ -83,7 +81,7 @@ public class PlayerSkinUpdater implements ServerEventListener {
|
|||
return;
|
||||
}
|
||||
|
||||
Map<String, BmMap> maps = plugin.getMaps();
|
||||
Map<String, BmMap> maps = plugin.getBlueMap().getMaps();
|
||||
if (maps == null) {
|
||||
Logger.global.logDebug("Could not update skin, since the plugin seems not to be ready.");
|
||||
return;
|
||||
|
@ -92,7 +90,7 @@ public class PlayerSkinUpdater implements ServerEventListener {
|
|||
BufferedImage playerHead = playerMarkerIconFactory.apply(playerUuid, skin.get());
|
||||
|
||||
for (BmMap map : maps.values()) {
|
||||
try (OutputStream out = map.getStorage().writeMeta(map.getId(), "assets/playerheads/" + playerUuid + ".png")) {
|
||||
try (OutputStream out = map.getStorage().asset("playerheads/" + playerUuid + ".png").write()) {
|
||||
ImageIO.write(playerHead, "png", out);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to write player skin to storage: " + playerUuid, ex);
|
||||
|
|
|
@ -24,11 +24,8 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.rendermanager;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@DebugDump
|
||||
public class CombinedRenderTask<T extends RenderTask> implements RenderTask {
|
||||
|
||||
private final String description;
|
||||
|
@ -84,13 +81,10 @@ public class CombinedRenderTask<T extends RenderTask> implements RenderTask {
|
|||
public boolean contains(RenderTask task) {
|
||||
if (this.equals(task)) return true;
|
||||
|
||||
if (task instanceof CombinedRenderTask) {
|
||||
CombinedRenderTask<?> combinedTask = (CombinedRenderTask<?>) task;
|
||||
|
||||
if (task instanceof CombinedRenderTask<?> combinedTask) {
|
||||
for (RenderTask subTask : combinedTask.tasks) {
|
||||
if (!this.contains(subTask)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -111,4 +105,5 @@ public class CombinedRenderTask<T extends RenderTask> implements RenderTask {
|
|||
if (this.currentTaskIndex >= this.tasks.size()) return Optional.empty();
|
||||
return Optional.ofNullable(this.tasks.get(this.currentTaskIndex).getDescription());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.rendermanager;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.common.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -55,19 +55,15 @@ public class MapPurgeTask implements RenderTask {
|
|||
// save lowres-tile-manager to clear/flush any buffered data
|
||||
this.map.getLowresTileManager().save();
|
||||
|
||||
try {
|
||||
// purge the map
|
||||
map.getStorage().purgeMap(map.getId(), progressInfo -> {
|
||||
this.progress = progressInfo.getProgress();
|
||||
return !this.cancelled;
|
||||
});
|
||||
// purge the map
|
||||
map.getStorage().delete(progress -> {
|
||||
this.progress = progress;
|
||||
return !this.cancelled;
|
||||
});
|
||||
|
||||
// reset texture gallery
|
||||
map.resetTextureGallery();
|
||||
} finally {
|
||||
// reset renderstate
|
||||
map.getRenderState().reset();
|
||||
}
|
||||
map.resetTextureGallery();
|
||||
map.getMapTileState().reset();
|
||||
map.getMapChunkState().reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,19 +25,21 @@
|
|||
package de.bluecolored.bluemap.common.rendermanager;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.MapTileState;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.TileInfoRegion;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.TileState;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@DebugDump
|
||||
public class MapUpdateTask extends CombinedRenderTask<RenderTask> {
|
||||
|
||||
private final BmMap map;
|
||||
|
@ -47,7 +49,7 @@ public class MapUpdateTask extends CombinedRenderTask<RenderTask> {
|
|||
this(map, getRegions(map));
|
||||
}
|
||||
|
||||
public MapUpdateTask(BmMap map, boolean force) {
|
||||
public MapUpdateTask(BmMap map, Predicate<TileState> force) {
|
||||
this(map, getRegions(map), force);
|
||||
}
|
||||
|
||||
|
@ -55,15 +57,15 @@ public class MapUpdateTask extends CombinedRenderTask<RenderTask> {
|
|||
this(map, getRegions(map, center, radius));
|
||||
}
|
||||
|
||||
public MapUpdateTask(BmMap map, Vector2i center, int radius, boolean force) {
|
||||
public MapUpdateTask(BmMap map, Vector2i center, int radius, Predicate<TileState> force) {
|
||||
this(map, getRegions(map, center, radius), force);
|
||||
}
|
||||
|
||||
public MapUpdateTask(BmMap map, Collection<Vector2i> regions) {
|
||||
this(map, regions, false);
|
||||
this(map, regions, s -> false);
|
||||
}
|
||||
|
||||
public MapUpdateTask(BmMap map, Collection<Vector2i> regions, boolean force) {
|
||||
public MapUpdateTask(BmMap map, Collection<Vector2i> regions, Predicate<TileState> force) {
|
||||
super("Update map '" + map.getId() + "'", createTasks(map, regions, force));
|
||||
this.map = map;
|
||||
this.regions = Collections.unmodifiableCollection(new ArrayList<>(regions));
|
||||
|
@ -77,7 +79,7 @@ public class MapUpdateTask extends CombinedRenderTask<RenderTask> {
|
|||
return regions;
|
||||
}
|
||||
|
||||
private static Collection<RenderTask> createTasks(BmMap map, Collection<Vector2i> regions, boolean force) {
|
||||
private static Collection<RenderTask> createTasks(BmMap map, Collection<Vector2i> regions, Predicate<TileState> force) {
|
||||
ArrayList<WorldRegionRenderTask> regionTasks = new ArrayList<>(regions.size());
|
||||
regions.forEach(region -> regionTasks.add(new WorldRegionRenderTask(map, region, force)));
|
||||
|
||||
|
@ -99,42 +101,65 @@ public class MapUpdateTask extends CombinedRenderTask<RenderTask> {
|
|||
return tasks;
|
||||
}
|
||||
|
||||
private static List<Vector2i> getRegions(BmMap map) {
|
||||
private static Collection<Vector2i> getRegions(BmMap map) {
|
||||
return getRegions(map, null, -1);
|
||||
}
|
||||
|
||||
private static List<Vector2i> getRegions(BmMap map, Vector2i center, int radius) {
|
||||
private static Collection<Vector2i> getRegions(BmMap map, Vector2i center, int radius) {
|
||||
World world = map.getWorld();
|
||||
Grid regionGrid = world.getRegionGrid();
|
||||
|
||||
Predicate<Vector2i> regionFilter = r -> {
|
||||
Vector2i cellMin = regionGrid.getCellMin(r);
|
||||
if (cellMin.getX() > map.getMapSettings().getMaxPos().getX()) return false;
|
||||
if (cellMin.getY() > map.getMapSettings().getMaxPos().getZ()) return false;
|
||||
|
||||
Vector2i cellMax = regionGrid.getCellMax(r);
|
||||
if (cellMax.getX() < map.getMapSettings().getMinPos().getX()) return false;
|
||||
return cellMax.getY() >= map.getMapSettings().getMinPos().getZ();
|
||||
};
|
||||
|
||||
Predicate<Vector2i> regionBoundsFilter = map.getMapSettings().getCellRenderBoundariesFilter(regionGrid, true);
|
||||
Predicate<Vector2i> regionRadiusFilter;
|
||||
if (center == null || radius < 0) {
|
||||
return world.listRegions().stream()
|
||||
.filter(regionFilter)
|
||||
.collect(Collectors.toList());
|
||||
regionRadiusFilter = r -> true;
|
||||
} else {
|
||||
Vector2i halfCell = regionGrid.getGridSize().div(2);
|
||||
long increasedRadiusSquared = (long) Math.pow(radius + Math.ceil(halfCell.length()), 2);
|
||||
regionRadiusFilter = r -> {
|
||||
Vector2i min = regionGrid.getCellMin(r);
|
||||
Vector2i regionCenter = min.add(halfCell);
|
||||
return regionCenter.toLong().distanceSquared(center.toLong()) <= increasedRadiusSquared;
|
||||
};
|
||||
}
|
||||
|
||||
List<Vector2i> regions = new ArrayList<>();
|
||||
Vector2i halfCell = regionGrid.getGridSize().div(2);
|
||||
long increasedRadiusSquared = (long) Math.pow(radius + Math.ceil(halfCell.length()), 2);
|
||||
Set<Vector2i> regions = new HashSet<>();
|
||||
|
||||
for (Vector2i region : world.listRegions()) {
|
||||
if (!regionFilter.test(region)) continue;
|
||||
// update all regions in the world-files
|
||||
world.listRegions().stream()
|
||||
.filter(regionBoundsFilter)
|
||||
.filter(regionRadiusFilter)
|
||||
.forEach(regions::add);
|
||||
|
||||
Vector2i min = regionGrid.getCellMin(region);
|
||||
Vector2i regionCenter = min.add(halfCell);
|
||||
|
||||
if (regionCenter.toLong().distanceSquared(center.toLong()) <= increasedRadiusSquared)
|
||||
regions.add(region);
|
||||
// also update regions that are present as map-tile-state files (they might have been rendered before but deleted now)
|
||||
// (a little hacky as we are operating on raw tile-state files -> maybe find a better way?)
|
||||
Grid tileGrid = map.getHiresModelManager().getTileGrid();
|
||||
Grid cellGrid = MapTileState.GRID.multiply(tileGrid);
|
||||
try (Stream<GridStorage.Cell> stream = map.getStorage().tileState().stream()) {
|
||||
stream
|
||||
.filter(c -> {
|
||||
// filter out files that are fully UNKNOWN/NOT_GENERATED
|
||||
// this avoids unnecessarily converting UNKNOWN tiles into NOT_GENERATED tiles on force-updates
|
||||
try (CompressedInputStream in = c.read()) {
|
||||
if (in == null) return false;
|
||||
TileState[] states = TileInfoRegion.loadPalette(in.decompress());
|
||||
for (TileState state : states) {
|
||||
if (
|
||||
state != TileState.UNKNOWN &&
|
||||
state != TileState.NOT_GENERATED
|
||||
) return true;
|
||||
}
|
||||
return false;
|
||||
} catch (IOException ignore) {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map(c -> new Vector2i(c.getX(), c.getZ()))
|
||||
.flatMap(v -> cellGrid.getIntersecting(v, regionGrid).stream())
|
||||
.filter(regionRadiusFilter)
|
||||
.forEach(regions::add);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load map tile state!", ex);
|
||||
}
|
||||
|
||||
return regions;
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.rendermanager;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
|
||||
import java.util.*;
|
||||
|
@ -35,19 +34,19 @@ 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 int id;
|
||||
private volatile boolean running;
|
||||
|
||||
@DebugDump private long lastTimeBusy;
|
||||
private long lastTimeBusy;
|
||||
|
||||
private final AtomicInteger nextWorkerThreadIndex;
|
||||
@DebugDump private final Collection<WorkerThread> workerThreads;
|
||||
private final Collection<WorkerThread> workerThreads;
|
||||
private final AtomicInteger busyCount;
|
||||
|
||||
private ProgressTracker progressTracker;
|
||||
private volatile boolean newTask;
|
||||
|
||||
@DebugDump private final LinkedList<RenderTask> renderTasks;
|
||||
private final LinkedList<RenderTask> renderTasks;
|
||||
|
||||
public RenderManager() {
|
||||
this.id = nextRenderManagerIndex.getAndIncrement();
|
||||
|
@ -106,9 +105,23 @@ public class RenderManager {
|
|||
}
|
||||
|
||||
public void awaitIdle() throws InterruptedException {
|
||||
awaitIdle(false);
|
||||
}
|
||||
|
||||
public void awaitIdle(boolean log) throws InterruptedException {
|
||||
synchronized (this.renderTasks) {
|
||||
while (!this.renderTasks.isEmpty())
|
||||
this.renderTasks.wait(10000);
|
||||
while (!this.renderTasks.isEmpty()) {
|
||||
this.renderTasks.wait(5000);
|
||||
|
||||
if (log) {
|
||||
RenderTask task = this.getCurrentRenderTask();
|
||||
if (task != null) {
|
||||
Logger.global.logInfo("Waiting for task '" + task.getDescription() + "' to stop.. (" +
|
||||
(Math.round(task.estimateProgress() * 10000) / 100.0) + "%)");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,21 +24,21 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.rendermanager;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.common.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class StorageDeleteTask implements RenderTask {
|
||||
|
||||
private final Storage storage;
|
||||
private final MapStorage storage;
|
||||
private final String mapId;
|
||||
|
||||
private volatile double progress;
|
||||
private volatile boolean hasMoreWork;
|
||||
private volatile boolean cancelled;
|
||||
|
||||
public StorageDeleteTask(Storage storage, String mapId) {
|
||||
public StorageDeleteTask(MapStorage storage, String mapId) {
|
||||
this.storage = Objects.requireNonNull(storage);
|
||||
this.mapId = Objects.requireNonNull(mapId);
|
||||
this.progress = 0d;
|
||||
|
@ -55,8 +55,8 @@ public class StorageDeleteTask implements RenderTask {
|
|||
if (this.cancelled) return;
|
||||
|
||||
// purge the map
|
||||
storage.purgeMap(mapId, progressInfo -> {
|
||||
this.progress = progressInfo.getProgress();
|
||||
storage.delete(progress -> {
|
||||
this.progress = progress;
|
||||
return !this.cancelled;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -26,208 +26,248 @@ package de.bluecolored.bluemap.common.rendermanager;
|
|||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector2l;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.common.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.ActionAndNextState;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.BoundsSituation;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.TileInfoRegion;
|
||||
import de.bluecolored.bluemap.core.map.renderstate.TileState;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.world.Region;
|
||||
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@DebugDump
|
||||
import static de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.Action.DELETE;
|
||||
import static de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.Action.RENDER;
|
||||
|
||||
public class WorldRegionRenderTask implements RenderTask {
|
||||
|
||||
private final BmMap map;
|
||||
private final Vector2i worldRegion;
|
||||
private final boolean force;
|
||||
@Getter private final BmMap map;
|
||||
@Getter private final Vector2i regionPos;
|
||||
@Getter private final Predicate<TileState> force;
|
||||
|
||||
private Deque<Vector2i> tiles;
|
||||
private int tileCount;
|
||||
private long startTime;
|
||||
private Grid regionGrid, chunkGrid, tileGrid;
|
||||
private Vector2i chunkMin, chunkMax, chunksSize;
|
||||
private Vector2i tileMin, tileMax, tileSize;
|
||||
|
||||
private int[] chunkHashes;
|
||||
private ActionAndNextState[] tileActions;
|
||||
|
||||
private volatile int nextTileX, nextTileZ;
|
||||
private volatile int atWork;
|
||||
private volatile boolean cancelled;
|
||||
private volatile boolean completed, cancelled;
|
||||
|
||||
public WorldRegionRenderTask(BmMap map, Vector2i worldRegion) {
|
||||
this(map, worldRegion, false);
|
||||
public WorldRegionRenderTask(BmMap map, Vector2i regionPos) {
|
||||
this(map, regionPos, false);
|
||||
}
|
||||
|
||||
public WorldRegionRenderTask(BmMap map, Vector2i worldRegion, boolean force) {
|
||||
public WorldRegionRenderTask(BmMap map, Vector2i regionPos, boolean force) {
|
||||
this(map, regionPos, s -> force);
|
||||
}
|
||||
|
||||
public WorldRegionRenderTask(BmMap map, Vector2i regionPos, Predicate<TileState> force) {
|
||||
this.map = map;
|
||||
this.worldRegion = worldRegion;
|
||||
this.regionPos = regionPos;
|
||||
this.force = force;
|
||||
|
||||
this.tiles = null;
|
||||
this.tileCount = -1;
|
||||
this.startTime = -1;
|
||||
this.nextTileX = 0;
|
||||
this.nextTileZ = 0;
|
||||
|
||||
this.atWork = 0;
|
||||
this.completed = false;
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
private synchronized void init() {
|
||||
Set<Vector2l> tileSet = new HashSet<>();
|
||||
startTime = System.currentTimeMillis();
|
||||
|
||||
//Logger.global.logInfo("Starting: " + worldRegion);
|
||||
// calculate bounds
|
||||
this.regionGrid = map.getWorld().getRegionGrid();
|
||||
this.chunkGrid = map.getWorld().getChunkGrid();
|
||||
this.tileGrid = map.getHiresModelManager().getTileGrid();
|
||||
this.chunkMin = regionGrid.getCellMin(regionPos, chunkGrid);
|
||||
this.chunkMax = regionGrid.getCellMax(regionPos, chunkGrid);
|
||||
this.chunksSize = chunkMax.sub(chunkMin).add(1, 1);
|
||||
this.tileMin = regionGrid.getCellMin(regionPos, tileGrid);
|
||||
this.tileMax = regionGrid.getCellMax(regionPos, tileGrid);
|
||||
this.tileSize = tileMax.sub(tileMin).add(1, 1);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// make sure chunk gets re-loaded from disk
|
||||
map.getWorld().invalidateChunkCache(chunk.getX(), chunk.getY());
|
||||
// load chunk-hash array
|
||||
int chunkMaxCount = chunksSize.getX() * chunksSize.getY();
|
||||
try {
|
||||
chunkHashes = new int[chunkMaxCount];
|
||||
map.getWorld().getRegion(regionPos.getX(), regionPos.getY())
|
||||
.iterateAllChunks( (ChunkConsumer.ListOnly) (x, z, timestamp) -> {
|
||||
chunkHashes[chunkIndex(
|
||||
x - chunkMin.getX(),
|
||||
z - chunkMin.getY()
|
||||
)] = timestamp;
|
||||
map.getWorld().invalidateChunkCache(x, z);
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load chunks for region " + regionPos, ex);
|
||||
cancel();
|
||||
}
|
||||
|
||||
Predicate<Vector2i> boundsTileFilter = t -> {
|
||||
Vector2i cellMin = tileGrid.getCellMin(t);
|
||||
if (cellMin.getX() > map.getMapSettings().getMaxPos().getX()) return false;
|
||||
if (cellMin.getY() > map.getMapSettings().getMaxPos().getZ()) return false;
|
||||
// check tile actions
|
||||
int tileMaxCount = tileSize.getX() * tileSize.getY();
|
||||
int tileRenderCount = 0;
|
||||
int tileDeleteCount = 0;
|
||||
tileActions = new ActionAndNextState[tileMaxCount];
|
||||
for (int x = 0; x < tileSize.getX(); x++) {
|
||||
for (int z = 0; z < tileSize.getY(); z++) {
|
||||
Vector2i tile = new Vector2i(tileMin.getX() + x, tileMin.getY() + z);
|
||||
TileState tileState = map.getMapTileState().get(tile.getX(), tile.getY()).getState();
|
||||
|
||||
Vector2i cellMax = tileGrid.getCellMax(t);
|
||||
if (cellMax.getX() < map.getMapSettings().getMinPos().getX()) return false;
|
||||
return cellMax.getY() >= map.getMapSettings().getMinPos().getZ();
|
||||
};
|
||||
int tileIndex = tileIndex(x, z);
|
||||
tileActions[tileIndex] = tileState.findActionAndNextState(
|
||||
force.test(tileState) || checkChunksHaveChanges(tile),
|
||||
checkTileBounds(tile)
|
||||
);
|
||||
|
||||
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
|
||||
.filter(boundsTileFilter)
|
||||
.filter(map.getTileFilter())
|
||||
.collect(Collectors.toCollection(ArrayDeque::new));
|
||||
if (tileActions[tileIndex].action() == RENDER)
|
||||
tileRenderCount++;
|
||||
if (tileActions[tileIndex].action() == DELETE)
|
||||
tileDeleteCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (tileRenderCount >= tileMaxCount * 0.75)
|
||||
map.getWorld().preloadRegionChunks(regionPos.getX(), regionPos.getY());
|
||||
|
||||
if (tileRenderCount + tileDeleteCount == 0)
|
||||
completed = true;
|
||||
|
||||
if (tiles.isEmpty()) complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWork() {
|
||||
if (cancelled) return;
|
||||
if (cancelled || completed) return;
|
||||
|
||||
Vector2i tile;
|
||||
int tileX, tileZ;
|
||||
|
||||
synchronized (this) {
|
||||
if (tiles == null) init();
|
||||
if (tiles.isEmpty()) return;
|
||||
if (cancelled || completed) return;
|
||||
|
||||
tile = tiles.pollFirst();
|
||||
tileX = nextTileX;
|
||||
tileZ = nextTileZ;
|
||||
|
||||
if (tileX == 0 && tileZ == 0) {
|
||||
init();
|
||||
if (cancelled || completed) return;
|
||||
}
|
||||
|
||||
nextTileX = tileX + 1;
|
||||
if (nextTileX >= tileSize.getX()) {
|
||||
nextTileZ = tileZ + 1;
|
||||
nextTileX = 0;
|
||||
}
|
||||
if (nextTileZ >= tileSize.getY()) {
|
||||
completed = true;
|
||||
}
|
||||
|
||||
this.atWork++;
|
||||
}
|
||||
|
||||
//Logger.global.logInfo("Working on " + worldRegion + " - Tile " + tile);
|
||||
if (tileRenderPreconditions(tile)) {
|
||||
map.renderTile(tile); // <- actual work
|
||||
}
|
||||
processTile(tileX, tileZ);
|
||||
|
||||
synchronized (this) {
|
||||
this.atWork--;
|
||||
|
||||
if (atWork <= 0 && tiles.isEmpty() && !cancelled) {
|
||||
if (atWork <= 0 && completed && !cancelled) {
|
||||
complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tileRenderPreconditions(Vector2i tile) {
|
||||
Grid tileGrid = map.getHiresModelManager().getTileGrid();
|
||||
Grid chunkGrid = map.getWorld().getChunkGrid();
|
||||
private void processTile(int x, int z) {
|
||||
Vector2i tile = new Vector2i(tileMin.getX() + x, tileMin.getY() + z);
|
||||
ActionAndNextState action = tileActions[tileIndex(x, z)];
|
||||
TileState resultState = TileState.RENDER_ERROR;
|
||||
|
||||
Vector2i minChunk = tileGrid.getCellMin(tile, chunkGrid);
|
||||
Vector2i maxChunk = tileGrid.getCellMax(tile, chunkGrid);
|
||||
try {
|
||||
|
||||
long minInhab = map.getMapSettings().getMinInhabitedTime();
|
||||
int minInhabRadius = map.getMapSettings().getMinInhabitedTimeRadius();
|
||||
if (minInhabRadius < 0) minInhabRadius = 0;
|
||||
if (minInhabRadius > 16) minInhabRadius = 16; // sanity check
|
||||
boolean isInhabited = false;
|
||||
resultState = switch (action.action()) {
|
||||
|
||||
for (int x = minChunk.getX(); x <= maxChunk.getX(); x++) {
|
||||
for (int z = minChunk.getY(); z <= maxChunk.getY(); z++) {
|
||||
Chunk chunk = map.getWorld().getChunk(x, z);
|
||||
if (!chunk.isGenerated()) return false;
|
||||
if (chunk.getInhabitedTime() >= minInhab) isInhabited = true;
|
||||
}
|
||||
}
|
||||
case NONE -> action.state();
|
||||
|
||||
if (minInhabRadius > 0 && !isInhabited) {
|
||||
for (int x = minChunk.getX() - minInhabRadius; x <= maxChunk.getX() + minInhabRadius; x++) {
|
||||
for (int z = minChunk.getY() - minInhabRadius; z <= maxChunk.getY() + minInhabRadius; z++) {
|
||||
Chunk chunk = map.getWorld().getChunk(x, z);
|
||||
if (chunk.getInhabitedTime() >= minInhab) {
|
||||
isInhabited = true;
|
||||
break;
|
||||
case RENDER -> {
|
||||
TileState failedState = checkTileRenderPreconditions(tile);
|
||||
if (failedState != null){
|
||||
map.unrenderTile(tile);
|
||||
yield failedState;
|
||||
}
|
||||
|
||||
map.renderTile(tile);
|
||||
yield action.state();
|
||||
}
|
||||
}
|
||||
|
||||
case DELETE -> {
|
||||
map.unrenderTile(tile);
|
||||
yield action.state();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} catch (Exception ex) {
|
||||
|
||||
Logger.global.logError("Error while processing map-tile " + tile + " for map '" + map.getId() + "'", ex);
|
||||
|
||||
} finally {
|
||||
|
||||
// mark tile with new state
|
||||
map.getMapTileState().set(tile.getX(), tile.getY(), new TileInfoRegion.TileInfo(
|
||||
(int) (System.currentTimeMillis() / 1000),
|
||||
resultState
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
return isInhabited;
|
||||
}
|
||||
|
||||
private void complete() {
|
||||
map.getRenderState().setRenderTime(worldRegion, startTime);
|
||||
private synchronized void complete() {
|
||||
// save chunk-hashes
|
||||
if (chunkHashes != null) {
|
||||
for (int x = 0; x < chunksSize.getX(); x++) {
|
||||
for (int z = 0; z < chunksSize.getY(); z++) {
|
||||
int hash = chunkHashes[chunkIndex(x, z)];
|
||||
map.getMapChunkState().set(chunkMin.getX() + x, chunkMin.getY() + z, hash);
|
||||
}
|
||||
}
|
||||
chunkHashes = null;
|
||||
}
|
||||
|
||||
//Logger.global.logInfo("Done with: " + worldRegion);
|
||||
// save map (at most, every minute)
|
||||
map.save(TimeUnit.MINUTES.toMillis(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
@DebugDump
|
||||
public synchronized boolean hasMoreWork() {
|
||||
return !cancelled && (tiles == null || !tiles.isEmpty());
|
||||
return !completed && !cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DebugDump
|
||||
public double estimateProgress() {
|
||||
if (tiles == null) return 0;
|
||||
if (tileCount == 0) return 1;
|
||||
|
||||
double remainingTiles = tiles.size();
|
||||
return 1 - (remainingTiles / this.tileCount);
|
||||
if (tileSize == null) return 0;
|
||||
return Math.min((double) (nextTileZ * tileSize.getX() + nextTileX) / (tileSize.getX() * tileSize.getY()), 1);
|
||||
}
|
||||
|
||||
@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() + "'";
|
||||
return "Update region " + regionPos + " for map '" + map.getId() + "'";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -235,19 +275,101 @@ public class WorldRegionRenderTask implements RenderTask {
|
|||
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);
|
||||
return force == that.force && map.getId().equals(that.map.getId()) && regionPos.equals(that.regionPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return worldRegion.hashCode();
|
||||
return regionPos.hashCode();
|
||||
}
|
||||
|
||||
private int chunkIndex(int x, int z) {
|
||||
return z * chunksSize.getX() + x;
|
||||
}
|
||||
|
||||
private int tileIndex(int x, int z) {
|
||||
return z * tileSize.getX() + x;
|
||||
}
|
||||
|
||||
private boolean checkChunksHaveChanges(Vector2i tile) {
|
||||
int minX = tileGrid.getCellMinX(tile.getX(), chunkGrid),
|
||||
maxX = tileGrid.getCellMaxX(tile.getX(), chunkGrid),
|
||||
minZ = tileGrid.getCellMinY(tile.getY(), chunkGrid),
|
||||
maxZ = tileGrid.getCellMaxY(tile.getY(), chunkGrid);
|
||||
|
||||
for (int chunkX = minX; chunkX <= maxX; chunkX++) {
|
||||
for (int chunkZ = minZ; chunkZ <= maxZ; chunkZ++) {
|
||||
int dx = chunkX - chunkMin.getX();
|
||||
int dz = chunkZ - chunkMin.getY();
|
||||
|
||||
// only check hash for chunks inside the current region
|
||||
if (
|
||||
chunkX >= chunkMin.getX() && chunkX <= chunkMax.getX() &&
|
||||
chunkZ >= chunkMin.getY() && chunkZ <= chunkMax.getY()
|
||||
) {
|
||||
int hash = chunkHashes[chunkIndex(dx, dz)];
|
||||
int lastHash = map.getMapChunkState().get(chunkX, chunkZ);
|
||||
|
||||
if (lastHash != hash) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private BoundsSituation checkTileBounds(Vector2i tile) {
|
||||
boolean isInsideBounds = map.getMapSettings().isInsideRenderBoundaries(tile, tileGrid, true);
|
||||
if (!isInsideBounds) return BoundsSituation.OUTSIDE;
|
||||
|
||||
boolean isFullyInsideBounds = map.getMapSettings().isInsideRenderBoundaries(tile, tileGrid, false);
|
||||
return isFullyInsideBounds ? BoundsSituation.INSIDE : BoundsSituation.EDGE;
|
||||
}
|
||||
|
||||
private @Nullable TileState checkTileRenderPreconditions(Vector2i tile) {
|
||||
boolean chunksAreInhabited = false;
|
||||
|
||||
long minInhabitedTime = map.getMapSettings().getMinInhabitedTime();
|
||||
int minInhabitedTimeRadius = map.getMapSettings().getMinInhabitedTimeRadius();
|
||||
boolean requireLight = !map.getMapSettings().isIgnoreMissingLightData();
|
||||
|
||||
int minX = tileGrid.getCellMinX(tile.getX(), chunkGrid),
|
||||
maxX = tileGrid.getCellMaxX(tile.getX(), chunkGrid),
|
||||
minZ = tileGrid.getCellMinY(tile.getY(), chunkGrid),
|
||||
maxZ = tileGrid.getCellMaxY(tile.getY(), chunkGrid);
|
||||
|
||||
for (int chunkX = minX; chunkX <= maxX; chunkX++) {
|
||||
for (int chunkZ = minZ; chunkZ <= maxZ; chunkZ++) {
|
||||
Chunk chunk = map.getWorld().getChunk(chunkX, chunkZ);
|
||||
if (chunk == Chunk.ERRORED_CHUNK) return TileState.CHUNK_ERROR;
|
||||
if (!chunk.isGenerated()) return TileState.NOT_GENERATED;
|
||||
if (requireLight && !chunk.hasLightData()) return TileState.MISSING_LIGHT;
|
||||
if (chunk.getInhabitedTime() >= minInhabitedTime) chunksAreInhabited = true;
|
||||
}
|
||||
}
|
||||
|
||||
// second pass for increased inhabited-time-radius
|
||||
if (!chunksAreInhabited && minInhabitedTimeRadius > 0) {
|
||||
inhabitedRadiusCheck:
|
||||
for (int chunkX = minX - minInhabitedTimeRadius; chunkX <= maxX + minInhabitedTimeRadius; chunkX++) {
|
||||
for (int chunkZ = minZ - minInhabitedTimeRadius; chunkZ <= maxZ + minInhabitedTimeRadius; chunkZ++) {
|
||||
Chunk chunk = map.getWorld().getChunk(chunkX, chunkZ);
|
||||
if (chunk.getInhabitedTime() >= minInhabitedTime) {
|
||||
chunksAreInhabited = true;
|
||||
break inhabitedRadiusCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chunksAreInhabited ? null : TileState.LOW_INHABITED_TIME;
|
||||
}
|
||||
|
||||
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());
|
||||
Vector2l task1Rel = new Vector2l(task1.regionPos.getX() - centerRegion.getX(), task1.regionPos.getY() - centerRegion.getY());
|
||||
Vector2l task2Rel = new Vector2l(task2.regionPos.getX() - centerRegion.getX(), task2.regionPos.getY() - centerRegion.getY());
|
||||
return compareVec2L(task1Rel, task2Rel);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public interface Player {
|
|||
|
||||
Text getName();
|
||||
|
||||
String getWorld();
|
||||
ServerWorld getWorld();
|
||||
|
||||
Vector3d getPosition();
|
||||
|
||||
|
@ -48,8 +48,6 @@ public interface Player {
|
|||
|
||||
int getBlockLight();
|
||||
|
||||
boolean isOnline();
|
||||
|
||||
/**
|
||||
* Return <code>true</code> if the player is sneaking.
|
||||
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
|
||||
|
|
|
@ -24,43 +24,20 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.serverinterface;
|
||||
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.common.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.util.Tristate;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface ServerInterface {
|
||||
public interface Server {
|
||||
|
||||
@DebugDump
|
||||
MinecraftVersion getMinecraftVersion();
|
||||
|
||||
/**
|
||||
* Registers a ServerEventListener, every method of this interface should be called on the specified events
|
||||
*/
|
||||
void registerListener(ServerEventListener listener);
|
||||
|
||||
/**
|
||||
* Removes all registered listeners
|
||||
*/
|
||||
void unregisterAllListeners();
|
||||
|
||||
default Optional<ServerWorld> getWorld(Path worldFolder) {
|
||||
Path normalizedWorldFolder = worldFolder.toAbsolutePath().normalize();
|
||||
return getLoadedWorlds().stream()
|
||||
.filter(world -> world.getSaveFolder().toAbsolutePath().normalize().equals(normalizedWorldFolder))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
default Optional<ServerWorld> getWorld(Object world) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@DebugDump
|
||||
Collection<ServerWorld> getLoadedWorlds();
|
||||
@Nullable String getMinecraftVersion();
|
||||
|
||||
/**
|
||||
* Returns the Folder containing the configurations for the plugin
|
||||
|
@ -82,6 +59,40 @@ public interface ServerInterface {
|
|||
return Tristate.UNDEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct {@link ServerWorld} for a {@link World} if there is any.
|
||||
*/
|
||||
default Optional<ServerWorld> getServerWorld(World world) {
|
||||
if (world instanceof MCAWorld) {
|
||||
MCAWorld mcaWorld = (MCAWorld) world;
|
||||
return getLoadedServerWorlds().stream()
|
||||
.filter(serverWorld ->
|
||||
serverWorld.getWorldFolder().toAbsolutePath().normalize()
|
||||
.equals(mcaWorld.getWorldFolder().toAbsolutePath().normalize()) &&
|
||||
serverWorld.getDimension().equals(mcaWorld.getDimension())
|
||||
)
|
||||
.findAny();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct {@link ServerWorld} for any Object if there is any, this should return the correct ServerWorld
|
||||
* for any implementation-specific object that represent or identify a world in any way.<br>
|
||||
* Used for the API implementation.
|
||||
*/
|
||||
default Optional<ServerWorld> getServerWorld(Object world) {
|
||||
if (world instanceof World)
|
||||
return getServerWorld((World) world);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all loaded worlds of this server.
|
||||
*/
|
||||
@DebugDump
|
||||
Collection<ServerWorld> getLoadedServerWorlds();
|
||||
|
||||
/**
|
||||
* Returns a collection of the states of players that are currently online
|
||||
*/
|
||||
|
@ -89,9 +100,13 @@ public interface ServerInterface {
|
|||
Collection<Player> getOnlinePlayers();
|
||||
|
||||
/**
|
||||
* Returns the state of the player with that UUID if present<br>
|
||||
* this method is only guaranteed to return a {@link Player} if the player is currently online.
|
||||
* Registers a ServerEventListener, every method of this interface should be called on the specified events
|
||||
*/
|
||||
Optional<Player> getPlayer(UUID uuid);
|
||||
void registerListener(ServerEventListener listener);
|
||||
|
||||
/**
|
||||
* Removes all registered listeners
|
||||
*/
|
||||
void unregisterAllListeners();
|
||||
|
||||
}
|
|
@ -24,8 +24,6 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.serverinterface;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface ServerEventListener {
|
||||
|
@ -34,6 +32,4 @@ public interface ServerEventListener {
|
|||
|
||||
default void onPlayerLeave(UUID playerUuid) {};
|
||||
|
||||
default void onChatMessage(Text message) {};
|
||||
|
||||
}
|
||||
|
|
|
@ -24,41 +24,24 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.serverinterface;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.common.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ServerWorld {
|
||||
|
||||
@DebugDump
|
||||
default Optional<String> getId() {
|
||||
return Optional.empty();
|
||||
}
|
||||
Path getWorldFolder();
|
||||
|
||||
@DebugDump
|
||||
default Optional<String> getName() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@DebugDump
|
||||
Path getSaveFolder();
|
||||
|
||||
@DebugDump
|
||||
default Dimension getDimension() {
|
||||
Path saveFolder = getSaveFolder();
|
||||
String lastName = saveFolder.getFileName().toString();
|
||||
if (lastName.equals("DIM-1")) return Dimension.NETHER;
|
||||
if (lastName.equals("DIM1")) return Dimension.END;
|
||||
return Dimension.OVERWORLD;
|
||||
}
|
||||
Key getDimension();
|
||||
|
||||
/**
|
||||
* Attempts to persist all changes that have been made in a world to disk.
|
||||
*
|
||||
* @return <code>true</code> if the changes have been successfully persisted, <code>false</code> if this operation is not supported by the implementation
|
||||
*
|
||||
* @throws IOException if something went wrong trying to persist the changes
|
||||
*/
|
||||
default boolean persistWorldChanges() throws IOException {
|
||||
|
|
|
@ -29,11 +29,15 @@ import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
|||
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpStatusCode;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter @Setter
|
||||
public class BlueMapResponseModifier implements HttpRequestHandler {
|
||||
|
||||
private final HttpRequestHandler delegate;
|
||||
private final String serverName;
|
||||
private @NonNull HttpRequestHandler delegate;
|
||||
private @NonNull String serverName;
|
||||
|
||||
public BlueMapResponseModifier(HttpRequestHandler delegate) {
|
||||
this.delegate = delegate;
|
||||
|
|
|
@ -25,37 +25,45 @@
|
|||
package de.bluecolored.bluemap.common.web;
|
||||
|
||||
import de.bluecolored.bluemap.common.web.http.*;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter @Setter
|
||||
public class FileRequestHandler implements HttpRequestHandler {
|
||||
|
||||
private final Path webRoot;
|
||||
private final File emptyTileFile;
|
||||
private @NonNull Path webRoot;
|
||||
|
||||
public FileRequestHandler(Path webRoot) {
|
||||
this.webRoot = webRoot.normalize();
|
||||
this.emptyTileFile = webRoot.resolve("assets").resolve("emptyTile.json").toFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse handle(HttpRequest request) {
|
||||
if (!request.getMethod().equalsIgnoreCase("GET"))
|
||||
return new HttpResponse(HttpStatusCode.BAD_REQUEST);
|
||||
return generateResponse(request);
|
||||
|
||||
try {
|
||||
return generateResponse(request);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to serve file", e);
|
||||
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpResponse generateResponse(HttpRequest request) {
|
||||
private HttpResponse generateResponse(HttpRequest request) throws IOException {
|
||||
String path = request.getPath();
|
||||
|
||||
// normalize path
|
||||
|
@ -74,53 +82,49 @@ public class FileRequestHandler implements HttpRequestHandler {
|
|||
return new HttpResponse(HttpStatusCode.FORBIDDEN);
|
||||
}
|
||||
|
||||
File file = filePath.toFile();
|
||||
|
||||
// redirect to have correct relative paths
|
||||
if (file.isDirectory() && !request.getPath().endsWith("/")) {
|
||||
if (Files.isDirectory(filePath) && !request.getPath().endsWith("/")) {
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.SEE_OTHER);
|
||||
response.addHeader("Location", "/" + path + "/" + (request.getGETParamString().isEmpty() ? "" : "?" + request.getGETParamString()));
|
||||
return response;
|
||||
}
|
||||
|
||||
// default to index.html
|
||||
if (!file.exists() || file.isDirectory()){
|
||||
file = new File(filePath + "/index.html");
|
||||
if (!Files.exists(filePath) || Files.isDirectory(filePath)){
|
||||
filePath = filePath.resolve("index.html");
|
||||
}
|
||||
|
||||
// send empty tile-file if tile not exists
|
||||
if (!file.exists() && file.toPath().startsWith(webRoot.resolve("maps"))){
|
||||
file = emptyTileFile;
|
||||
}
|
||||
|
||||
if (!file.exists() || file.isDirectory()) {
|
||||
if (!Files.exists(filePath) || Files.isDirectory(filePath)){
|
||||
return new HttpResponse(HttpStatusCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
// don't send php files
|
||||
if (file.getName().endsWith(".php")) {
|
||||
if (filePath.getFileName().toString().endsWith(".php")) {
|
||||
return new HttpResponse(HttpStatusCode.FORBIDDEN);
|
||||
}
|
||||
|
||||
// check if file is still in web-root and is not a directory
|
||||
if (!file.toPath().normalize().startsWith(webRoot) || file.isDirectory()){
|
||||
if (!filePath.normalize().startsWith(webRoot) || Files.isDirectory(filePath)){
|
||||
return new HttpResponse(HttpStatusCode.FORBIDDEN);
|
||||
}
|
||||
|
||||
// check modified
|
||||
long lastModified = file.lastModified();
|
||||
long lastModified = Files.getLastModifiedTime(filePath).to(TimeUnit.MILLISECONDS);
|
||||
HttpHeader modHeader = request.getHeader("If-Modified-Since");
|
||||
if (modHeader != null){
|
||||
try {
|
||||
long since = stringToTimestamp(modHeader.getValue());
|
||||
long since = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(modHeader.getValue())).toEpochMilli();
|
||||
if (since + 1000 >= lastModified){
|
||||
return new HttpResponse(HttpStatusCode.NOT_MODIFIED);
|
||||
}
|
||||
} catch (IllegalArgumentException ignored){}
|
||||
} catch (DateTimeParseException ignored){}
|
||||
}
|
||||
|
||||
//check ETag
|
||||
String eTag = Long.toHexString(file.length()) + Integer.toHexString(file.hashCode()) + Long.toHexString(lastModified);
|
||||
String eTag =
|
||||
Long.toHexString(Files.size(filePath)) +
|
||||
Integer.toHexString(filePath.hashCode()) +
|
||||
Long.toHexString(lastModified);
|
||||
HttpHeader etagHeader = request.getHeader("If-None-Match");
|
||||
if (etagHeader != null){
|
||||
if(etagHeader.getValue().equals(eTag)) {
|
||||
|
@ -131,12 +135,15 @@ public class FileRequestHandler implements HttpRequestHandler {
|
|||
//create response
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.addHeader("ETag", eTag);
|
||||
if (lastModified > 0) response.addHeader("Last-Modified", timestampToString(lastModified));
|
||||
if (lastModified > 0) response.addHeader("Last-Modified", DateTimeFormatter.RFC_1123_DATE_TIME.format(Instant
|
||||
.ofEpochMilli(lastModified)
|
||||
.atOffset(ZoneOffset.UTC)
|
||||
));
|
||||
response.addHeader("Cache-Control", "public");
|
||||
response.addHeader("Cache-Control", "max-age=" + TimeUnit.HOURS.toSeconds(1));
|
||||
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
||||
|
||||
//add content type header
|
||||
String filetype = file.getName();
|
||||
String filetype = filePath.getFileName().toString();
|
||||
int pointIndex = filetype.lastIndexOf('.');
|
||||
if (pointIndex >= 0) filetype = filetype.substring(pointIndex + 1);
|
||||
String contentType = toContentType(filetype);
|
||||
|
@ -144,80 +151,29 @@ public class FileRequestHandler implements HttpRequestHandler {
|
|||
|
||||
//send response
|
||||
try {
|
||||
response.setData(new FileInputStream(file));
|
||||
response.setData(Files.newInputStream(filePath));
|
||||
return response;
|
||||
} catch (FileNotFoundException e) {
|
||||
return new HttpResponse(HttpStatusCode.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
private static String timestampToString(long time){
|
||||
return DateFormatUtils.format(time, "EEE, dd MMM yyy HH:mm:ss 'GMT'", TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
|
||||
}
|
||||
|
||||
private static long stringToTimestamp(String timeString) throws IllegalArgumentException {
|
||||
try {
|
||||
int day = Integer.parseInt(timeString.substring(5, 7));
|
||||
|
||||
int month = Calendar.JANUARY;
|
||||
switch (timeString.substring(8, 11)){
|
||||
case "Feb" : month = Calendar.FEBRUARY; break;
|
||||
case "Mar" : month = Calendar.MARCH; break;
|
||||
case "Apr" : month = Calendar.APRIL; break;
|
||||
case "May" : month = Calendar.MAY; break;
|
||||
case "Jun" : month = Calendar.JUNE; break;
|
||||
case "Jul" : month = Calendar.JULY; break;
|
||||
case "Aug" : month = Calendar.AUGUST; break;
|
||||
case "Sep" : month = Calendar.SEPTEMBER; break;
|
||||
case "Oct" : month = Calendar.OCTOBER; break;
|
||||
case "Nov" : month = Calendar.NOVEMBER; break;
|
||||
case "Dec" : month = Calendar.DECEMBER; break;
|
||||
}
|
||||
int year = Integer.parseInt(timeString.substring(12, 16));
|
||||
int hour = Integer.parseInt(timeString.substring(17, 19));
|
||||
int min = Integer.parseInt(timeString.substring(20, 22));
|
||||
int sec = Integer.parseInt(timeString.substring(23, 25));
|
||||
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
||||
cal.set(year, month, day, hour, min, sec);
|
||||
return cal.getTimeInMillis();
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e){
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String toContentType(String fileEnding) {
|
||||
String contentType = "text/plain";
|
||||
switch (fileEnding) {
|
||||
case "json" :
|
||||
contentType = "application/json";
|
||||
break;
|
||||
case "png" :
|
||||
contentType = "image/png";
|
||||
break;
|
||||
case "jpg" :
|
||||
case "jpeg" :
|
||||
case "jpe" :
|
||||
contentType = "image/jpeg";
|
||||
break;
|
||||
case "svg" :
|
||||
contentType = "image/svg+xml";
|
||||
break;
|
||||
case "css" :
|
||||
contentType = "text/css";
|
||||
break;
|
||||
case "js" :
|
||||
contentType = "text/javascript";
|
||||
break;
|
||||
case "html" :
|
||||
case "htm" :
|
||||
case "shtml" :
|
||||
contentType = "text/html";
|
||||
break;
|
||||
case "xml" :
|
||||
contentType = "text/xml";
|
||||
break;
|
||||
}
|
||||
return contentType;
|
||||
return switch (fileEnding) {
|
||||
case "json" -> "application/json";
|
||||
case "png" -> "image/png";
|
||||
case "jpg",
|
||||
"jpeg",
|
||||
"jpe" -> "image/jpeg";
|
||||
case "svg" -> "image/svg+xml";
|
||||
case "css" -> "text/css";
|
||||
case "js" -> "text/javascript";
|
||||
case "html",
|
||||
"htm",
|
||||
"shtml" -> "text/html";
|
||||
case "xml" -> "text/xml";
|
||||
default -> "text/plain";
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,12 +28,16 @@ import de.bluecolored.bluemap.common.web.http.HttpRequest;
|
|||
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpStatusCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Getter @Setter
|
||||
public class JsonDataRequestHandler implements HttpRequestHandler {
|
||||
|
||||
private final Supplier<String> dataSupplier;
|
||||
private @NonNull Supplier<String> dataSupplier;
|
||||
|
||||
public JsonDataRequestHandler(Supplier<String> dataSupplier) {
|
||||
this.dataSupplier = dataSupplier;
|
||||
|
|
|
@ -26,12 +26,18 @@ package de.bluecolored.bluemap.common.web;
|
|||
|
||||
import de.bluecolored.bluemap.common.web.http.*;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter @Setter
|
||||
@AllArgsConstructor
|
||||
public class LoggingRequestHandler implements HttpRequestHandler {
|
||||
|
||||
private final HttpRequestHandler delegate;
|
||||
private final Logger logger;
|
||||
private final String format;
|
||||
private @NonNull HttpRequestHandler delegate;
|
||||
private @NonNull String format;
|
||||
private @NonNull Logger logger;
|
||||
|
||||
public LoggingRequestHandler(HttpRequestHandler delegate) {
|
||||
this(delegate, Logger.global);
|
||||
|
@ -45,12 +51,6 @@ public class LoggingRequestHandler implements HttpRequestHandler {
|
|||
this(delegate, format, Logger.global);
|
||||
}
|
||||
|
||||
public LoggingRequestHandler(HttpRequestHandler delegate, String format, Logger logger) {
|
||||
this.delegate = delegate;
|
||||
this.format = format;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse handle(HttpRequest request) {
|
||||
|
||||
|
|
|
@ -27,8 +27,10 @@ package de.bluecolored.bluemap.common.web;
|
|||
import de.bluecolored.bluemap.common.config.PluginConfig;
|
||||
import de.bluecolored.bluemap.common.live.LiveMarkersDataSupplier;
|
||||
import de.bluecolored.bluemap.common.live.LivePlayersDataSupplier;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.common.serverinterface.Server;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
@ -38,21 +40,21 @@ import java.util.function.Supplier;
|
|||
|
||||
public class MapRequestHandler extends RoutingRequestHandler {
|
||||
|
||||
public MapRequestHandler(BmMap map, ServerInterface serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
||||
this(map.getId(), map.getStorage(),
|
||||
new LivePlayersDataSupplier(serverInterface, pluginConfig, map.getWorldId(), playerFilter),
|
||||
public MapRequestHandler(BmMap map, Server serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
||||
this(map.getStorage(),
|
||||
createPlayersDataSupplier(map, serverInterface, pluginConfig, playerFilter),
|
||||
new LiveMarkersDataSupplier(map.getMarkerSets()));
|
||||
}
|
||||
|
||||
public MapRequestHandler(String mapId, Storage mapStorage) {
|
||||
this(mapId, mapStorage, null, null);
|
||||
public MapRequestHandler(MapStorage mapStorage) {
|
||||
this(mapStorage, null, null);
|
||||
}
|
||||
|
||||
public MapRequestHandler(String mapId, Storage mapStorage,
|
||||
public MapRequestHandler(MapStorage mapStorage,
|
||||
@Nullable Supplier<String> livePlayersDataSupplier,
|
||||
@Nullable Supplier<String> liveMarkerDataSupplier) {
|
||||
|
||||
register(".*", new MapStorageRequestHandler(mapId, mapStorage));
|
||||
register(".*", new MapStorageRequestHandler(mapStorage));
|
||||
|
||||
if (livePlayersDataSupplier != null) {
|
||||
register("live/players\\.json", "", new JsonDataRequestHandler(
|
||||
|
@ -67,4 +69,10 @@ public class MapRequestHandler extends RoutingRequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private static @Nullable LivePlayersDataSupplier createPlayersDataSupplier(BmMap map, Server serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
||||
ServerWorld world = serverInterface.getServerWorld(map.getWorld()).orElse(null);
|
||||
if (world == null) return null;
|
||||
return new LivePlayersDataSupplier(serverInterface, pluginConfig, world, playerFilter);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,41 +24,39 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.web;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.api.ContentTypeRegistry;
|
||||
import de.bluecolored.bluemap.common.web.http.*;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpRequest;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpStatusCode;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.storage.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.TileInfo;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter @Setter
|
||||
public class MapStorageRequestHandler implements HttpRequestHandler {
|
||||
|
||||
private static final Pattern TILE_PATTERN = Pattern.compile("tiles/([\\d/]+)/x(-?[\\d/]+)z(-?[\\d/]+).*");
|
||||
|
||||
private final String mapId;
|
||||
private final Storage mapStorage;
|
||||
|
||||
|
||||
public MapStorageRequestHandler(BmMap map) {
|
||||
this.mapId = map.getId();
|
||||
this.mapStorage = map.getStorage();
|
||||
}
|
||||
|
||||
public MapStorageRequestHandler(String mapId, Storage mapStorage) {
|
||||
this.mapId = mapId;
|
||||
this.mapStorage = mapStorage;
|
||||
}
|
||||
private @NonNull MapStorage mapStorage;
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
public HttpResponse handle(HttpRequest request) {
|
||||
String path = request.getPath();
|
||||
|
@ -75,53 +73,36 @@ public class MapStorageRequestHandler implements HttpRequestHandler {
|
|||
int lod = Integer.parseInt(tileMatcher.group(1));
|
||||
int x = Integer.parseInt(tileMatcher.group(2).replace("/", ""));
|
||||
int z = Integer.parseInt(tileMatcher.group(3).replace("/", ""));
|
||||
Optional<TileInfo> optTileInfo = mapStorage.readMapTileInfo(mapId, lod, new Vector2i(x, z));
|
||||
|
||||
if (optTileInfo.isPresent()) {
|
||||
TileInfo tileInfo = optTileInfo.get();
|
||||
GridStorage gridStorage = lod == 0 ? mapStorage.hiresTiles() : mapStorage.lowresTiles(lod);
|
||||
CompressedInputStream in = gridStorage.read(x, z);
|
||||
if (in == null) return new HttpResponse(HttpStatusCode.NO_CONTENT);
|
||||
|
||||
// check e-tag
|
||||
String eTag = calculateETag(path, tileInfo);
|
||||
HttpHeader etagHeader = request.getHeader("If-None-Match");
|
||||
if (etagHeader != null){
|
||||
if(etagHeader.getValue().equals(eTag)) {
|
||||
return new HttpResponse(HttpStatusCode.NOT_MODIFIED);
|
||||
}
|
||||
}
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.addHeader("Cache-Control", "public");
|
||||
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
||||
|
||||
// check modified-since
|
||||
long lastModified = tileInfo.getLastModified();
|
||||
HttpHeader modHeader = request.getHeader("If-Modified-Since");
|
||||
if (modHeader != null){
|
||||
try {
|
||||
long since = stringToTimestamp(modHeader.getValue());
|
||||
if (since + 1000 >= lastModified){
|
||||
return new HttpResponse(HttpStatusCode.NOT_MODIFIED);
|
||||
}
|
||||
} catch (IllegalArgumentException ignored){}
|
||||
}
|
||||
if (lod == 0) response.addHeader("Content-Type", "application/octet-stream");
|
||||
else response.addHeader("Content-Type", "image/png");
|
||||
|
||||
CompressedInputStream compressedIn = tileInfo.readMapTile();
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.addHeader("ETag", eTag);
|
||||
if (lastModified > 0)
|
||||
response.addHeader("Last-Modified", timestampToString(lastModified));
|
||||
|
||||
if (lod == 0) response.addHeader("Content-Type", "application/json");
|
||||
else response.addHeader("Content-Type", "image/png");
|
||||
|
||||
writeToResponse(compressedIn, response, request);
|
||||
return response;
|
||||
}
|
||||
writeToResponse(in, response, request);
|
||||
return response;
|
||||
}
|
||||
|
||||
// provide meta-data
|
||||
Optional<InputStream> optIn = mapStorage.readMeta(mapId, path);
|
||||
if (optIn.isPresent()) {
|
||||
CompressedInputStream compressedIn = new CompressedInputStream(optIn.get(), Compression.NONE);
|
||||
CompressedInputStream in = switch (path) {
|
||||
case "settings.json" -> mapStorage.settings().read();
|
||||
case "textures.json" -> mapStorage.textures().read();
|
||||
case "live/markers.json" -> mapStorage.markers().read();
|
||||
case "live/players.json" -> mapStorage.players().read();
|
||||
default -> path.startsWith("assets/") ? mapStorage.asset(path.substring(7)).read() : null;
|
||||
};
|
||||
if (in != null){
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.addHeader("Cache-Control", "public");
|
||||
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
||||
response.addHeader("Content-Type", ContentTypeRegistry.fromFileName(path));
|
||||
writeToResponse(compressedIn, response, request);
|
||||
writeToResponse(in, response, request);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
@ -131,41 +112,26 @@ public class MapStorageRequestHandler implements HttpRequestHandler {
|
|||
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if (path.endsWith(".png")) {
|
||||
return new HttpResponse(HttpStatusCode.NO_CONTENT);
|
||||
}
|
||||
|
||||
if (path.endsWith(".json")) {
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.addHeader("Content-Type", "application/json");
|
||||
response.setData("{}");
|
||||
return response;
|
||||
}
|
||||
|
||||
return new HttpResponse(HttpStatusCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
private String calculateETag(String path, TileInfo tileInfo) {
|
||||
return Long.toHexString(tileInfo.getSize()) + Integer.toHexString(path.hashCode()) + Long.toHexString(tileInfo.getLastModified());
|
||||
}
|
||||
|
||||
private void writeToResponse(CompressedInputStream data, HttpResponse response, HttpRequest request) throws IOException {
|
||||
Compression compression = data.getCompression();
|
||||
if (
|
||||
compression != Compression.NONE &&
|
||||
request.hasHeaderValue("Accept-Encoding", compression.getTypeId())
|
||||
request.hasHeaderValue("Accept-Encoding", compression.getId())
|
||||
) {
|
||||
response.addHeader("Content-Encoding", compression.getTypeId());
|
||||
response.addHeader("Content-Encoding", compression.getId());
|
||||
response.setData(data);
|
||||
} else if (
|
||||
compression != Compression.GZIP &&
|
||||
!response.hasHeaderValue("Content-Type", "image/png") &&
|
||||
request.hasHeaderValue("Accept-Encoding", Compression.GZIP.getTypeId())
|
||||
request.hasHeaderValue("Accept-Encoding", Compression.GZIP.getId())
|
||||
) {
|
||||
response.addHeader("Content-Encoding", Compression.GZIP.getTypeId());
|
||||
response.addHeader("Content-Encoding", Compression.GZIP.getId());
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
try (OutputStream os = Compression.GZIP.compress(byteOut)) {
|
||||
IOUtils.copyLarge(data.decompress(), os);
|
||||
data.decompress().transferTo(os);
|
||||
}
|
||||
byte[] compressedData = byteOut.toByteArray();
|
||||
response.setData(new ByteArrayInputStream(compressedData));
|
||||
|
@ -174,38 +140,4 @@ public class MapStorageRequestHandler implements HttpRequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private static String timestampToString(long time){
|
||||
return DateFormatUtils.format(time, "EEE, dd MMM yyy HH:mm:ss 'GMT'", TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
|
||||
}
|
||||
|
||||
private static long stringToTimestamp(String timeString) throws IllegalArgumentException {
|
||||
try {
|
||||
int day = Integer.parseInt(timeString.substring(5, 7));
|
||||
|
||||
int month = Calendar.JANUARY;
|
||||
switch (timeString.substring(8, 11)){
|
||||
case "Feb" : month = Calendar.FEBRUARY; break;
|
||||
case "Mar" : month = Calendar.MARCH; break;
|
||||
case "Apr" : month = Calendar.APRIL; break;
|
||||
case "May" : month = Calendar.MAY; break;
|
||||
case "Jun" : month = Calendar.JUNE; break;
|
||||
case "Jul" : month = Calendar.JULY; break;
|
||||
case "Aug" : month = Calendar.AUGUST; break;
|
||||
case "Sep" : month = Calendar.SEPTEMBER; break;
|
||||
case "Oct" : month = Calendar.OCTOBER; break;
|
||||
case "Nov" : month = Calendar.NOVEMBER; break;
|
||||
case "Dec" : month = Calendar.DECEMBER; break;
|
||||
}
|
||||
int year = Integer.parseInt(timeString.substring(12, 16));
|
||||
int hour = Integer.parseInt(timeString.substring(17, 19));
|
||||
int min = Integer.parseInt(timeString.substring(20, 22));
|
||||
int sec = Integer.parseInt(timeString.substring(23, 25));
|
||||
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
||||
cal.set(year, month, day, hour, min, sec);
|
||||
return cal.getTimeInMillis();
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e){
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,18 +28,24 @@ import de.bluecolored.bluemap.common.web.http.HttpRequest;
|
|||
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpStatusCode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public class RoutingRequestHandler implements HttpRequestHandler {
|
||||
|
||||
public LinkedList<Route> routes;
|
||||
private final Deque<Route> routes;
|
||||
|
||||
public RoutingRequestHandler() {
|
||||
this.routes = new LinkedList<>();
|
||||
this.routes = new ConcurrentLinkedDeque<>();
|
||||
}
|
||||
|
||||
public void register(@Language("RegExp") String pattern, HttpRequestHandler handler) {
|
||||
|
@ -77,36 +83,20 @@ public class RoutingRequestHandler implements HttpRequestHandler {
|
|||
return new HttpResponse(HttpStatusCode.BAD_REQUEST);
|
||||
}
|
||||
|
||||
private static class Route {
|
||||
@AllArgsConstructor
|
||||
@Getter @Setter
|
||||
public static class Route {
|
||||
|
||||
private final Pattern routePattern;
|
||||
private final HttpRequestHandler handler;
|
||||
private final String replacementRoute;
|
||||
private @NonNull Pattern routePattern;
|
||||
private @NonNull String replacementRoute;
|
||||
private @NonNull HttpRequestHandler handler;
|
||||
|
||||
public Route(Pattern routePattern, HttpRequestHandler handler) {
|
||||
public Route(@NonNull Pattern routePattern, @NonNull HttpRequestHandler handler) {
|
||||
this.routePattern = routePattern;
|
||||
this.replacementRoute = "$0";
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public Route(Pattern routePattern, String replacementRoute, HttpRequestHandler handler) {
|
||||
this.routePattern = routePattern;
|
||||
this.replacementRoute = replacementRoute;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public Pattern getRoutePattern() {
|
||||
return routePattern;
|
||||
}
|
||||
|
||||
public HttpRequestHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public String getReplacementRoute() {
|
||||
return replacementRoute;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -87,13 +87,20 @@ public class HttpConnection implements SelectionConsumer {
|
|||
() -> requestHandler.handle(request),
|
||||
responseHandlerExecutor
|
||||
);
|
||||
futureResponse.thenAccept(response -> {
|
||||
futureResponse.handle((response, error) -> {
|
||||
if (error != null) {
|
||||
Logger.global.logError("Unexpected error handling request", error);
|
||||
response = new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
try {
|
||||
response.read(channel); // do an initial read to trigger response sending intent
|
||||
this.response = response;
|
||||
} catch (IOException e) {
|
||||
handleIOException(channel, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -120,7 +127,26 @@ public class HttpConnection implements SelectionConsumer {
|
|||
|
||||
private void handleIOException(Channel channel, IOException e) {
|
||||
request.clear();
|
||||
response = null;
|
||||
|
||||
if (response != null) {
|
||||
try {
|
||||
response.close();
|
||||
} catch (IOException e2) {
|
||||
Logger.global.logWarning("Failed to close response: " + e2);
|
||||
}
|
||||
response = null;
|
||||
}
|
||||
|
||||
if (futureResponse != null) {
|
||||
futureResponse.thenAccept(response -> {
|
||||
try {
|
||||
response.close();
|
||||
} catch (IOException e2) {
|
||||
Logger.global.logWarning("Failed to close response: " + e2);
|
||||
}
|
||||
});
|
||||
futureResponse = null;
|
||||
}
|
||||
|
||||
Logger.global.logDebug("Failed to process selection: " + e);
|
||||
try {
|
||||
|
|
|
@ -24,11 +24,15 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.web.http;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HttpServer extends Server {
|
||||
|
||||
private final HttpRequestHandler requestHandler;
|
||||
@Getter @Setter
|
||||
private HttpRequestHandler requestHandler;
|
||||
|
||||
public HttpServer(HttpRequestHandler requestHandler) throws IOException {
|
||||
this.requestHandler = requestHandler;
|
||||
|
@ -37,10 +41,6 @@ public class HttpServer extends Server {
|
|||
@Override
|
||||
public SelectionConsumer createConnectionHandler() {
|
||||
return new HttpConnection(requestHandler);
|
||||
|
||||
// Enable async request handling ...
|
||||
// TODO: maybe find a better/separate executor than using bluemap's common thread-pool
|
||||
//return new HttpConnection(requestHandler, BlueMap.THREAD_POOL);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common.web.http;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum HttpStatusCode {
|
||||
|
||||
CONTINUE (100, "Continue"),
|
||||
|
@ -47,13 +50,8 @@ public enum HttpStatusCode {
|
|||
SERVICE_UNAVAILABLE (503, "Service Unavailable"),
|
||||
HTTP_VERSION_NOT_SUPPORTED (505, "HTTP Version not supported");
|
||||
|
||||
private int code;
|
||||
private String message;
|
||||
|
||||
private HttpStatusCode(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
public int getCode(){
|
||||
return code;
|
||||
|
|
|
@ -28,10 +28,12 @@ import de.bluecolored.bluemap.core.logger.Logger;
|
|||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class Server extends Thread implements Closeable, Runnable {
|
||||
|
||||
|
@ -52,7 +54,20 @@ public abstract class Server extends Thread implements Closeable, Runnable {
|
|||
server.bind(address);
|
||||
this.server.add(server);
|
||||
|
||||
Logger.global.logInfo("WebServer bound to: " + server.getLocalAddress());
|
||||
if (checkIfBoundToAllInterfaces(address)) {
|
||||
Logger.global.logInfo("WebServer bound to all network interfaces on port " + ((InetSocketAddress) address).getPort());
|
||||
} else {
|
||||
Logger.global.logInfo("WebServer bound to: " + server.getLocalAddress());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkIfBoundToAllInterfaces(SocketAddress address) {
|
||||
if (address instanceof InetSocketAddress) {
|
||||
InetSocketAddress inetAddress = (InetSocketAddress) address;
|
||||
return Objects.equals(inetAddress.getAddress(), new InetSocketAddress(0).getAddress());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
# By changing the setting (accept-download) below to TRUE you are indicating that you have accepted mojang's EULA (https://account.mojang.com/documents/minecraft_eula),
|
||||
# you confirm that you own a license to Minecraft (Java Edition)
|
||||
# and you agree that BlueMap will download and use a minecraft-client file (depending on the minecraft-version) from mojangs servers (https://launcher.mojang.com/) for you.
|
||||
# and you agree that BlueMap will download and use a minecraft-client file (depending on the minecraft-version) from mojangs servers (https://piston-meta.mojang.com/) for you.
|
||||
# This file contains resources that belong to mojang and you must not redistribute it or do anything else that is not compliant with mojang's EULA.
|
||||
# BlueMap uses resources in this file to generate the 3D-Models used for the map and texture them. (BlueMap will not work without those resources.)
|
||||
# ${timestamp}
|
||||
|
|
|
@ -3,16 +3,20 @@
|
|||
## Map-Config ##
|
||||
## ##
|
||||
|
||||
# The name of this map
|
||||
# This defines the display name of this map, you can change this at any time.
|
||||
# Default is the id of this map
|
||||
name: "${name}"
|
||||
|
||||
# The path to the save-folder of the world to render.
|
||||
# (If this is not defined (commented out or removed), the map will be only registered to the web-server and the web-app
|
||||
# but not rendered or loaded by BlueMap. This can be used to display a map that has been rendered somewhere else.)
|
||||
world: "${world}"
|
||||
|
||||
# The dimension of the world. Can be "minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"
|
||||
# or any dimension-key introduced by a mod or datapack.
|
||||
dimension: "${dimension}"
|
||||
|
||||
# The display-name of this map -> how this map will be named on the webapp.
|
||||
# You can change this at any time.
|
||||
# Default is the id of this map
|
||||
name: "${name}"
|
||||
|
||||
# A lower value makes the map sorted first (in lists and menus), a higher value makes it sorted later.
|
||||
# The value needs to be an integer but it can be negative.
|
||||
# You can change this at any time.
|
||||
|
@ -24,25 +28,22 @@ sorting: ${sorting}
|
|||
# This defaults to the world-spawn if you don't set it.
|
||||
#start-pos: {x:500, z:-820}
|
||||
|
||||
# The color of thy sky as a hex-color
|
||||
# The color of the sky as a hex-color
|
||||
# You can change this at any time.
|
||||
# Default is "#7dabff"
|
||||
sky-color: "${sky-color}"
|
||||
|
||||
# The color of the void as a hex-color
|
||||
# You can change this at any time.
|
||||
# Default is "#000000"
|
||||
void-color: "${void-color}"
|
||||
|
||||
# Defines the ambient light-strength that every block is receiving, regardless of the sunlight/blocklight.
|
||||
# 0 is no ambient light, 1 is fully lighted.
|
||||
# You can change this at any time.
|
||||
# Default is 0
|
||||
ambient-light: ${ambient-light}
|
||||
|
||||
# Defines the skylight level that the sky of the world is emitting.
|
||||
# This should always be equivalent to the maximum in-game sky-light for that world!
|
||||
# If this is a normal overworld dimension, set this to 15 (max).
|
||||
# If this is a normal nether or end dimension, set this to 0 (min).
|
||||
# Changing this value requires a re-render of the map.
|
||||
# Default is 15
|
||||
world-sky-light: ${world-sky-light}
|
||||
|
||||
# BlueMap tries to omit all blocks that are below this Y-level and are not visible from above-ground.
|
||||
# More specific: Block-Faces that have a sunlight/skylight value of 0 are removed.
|
||||
# This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# The storage-type of this storage.
|
||||
# Depending on this setting, different config-entries are allowed/expected in this config file.
|
||||
# Don't change this value! (If you want a different storage-type, check out the other example-configs)
|
||||
storage-type: FILE
|
||||
storage-type: file
|
||||
|
||||
# The path to the folder on your file-system where bluemap will save the rendered map
|
||||
# The default is: "bluemap/web/maps"
|
||||
|
@ -14,7 +14,9 @@ root: "${root}"
|
|||
|
||||
# The compression-type that bluemap will use to compress generated map-data.
|
||||
# Available compression-types are:
|
||||
# - GZIP
|
||||
# - NONE
|
||||
# The default is: GZIP
|
||||
compression: GZIP
|
||||
# - gzip
|
||||
# - zstd
|
||||
# - deflate
|
||||
# - none
|
||||
# The default is: gzip
|
||||
compression: gzip
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# The storage-type of this storage.
|
||||
# Depending on this setting, different config-entries are allowed/expected in this config file.
|
||||
# Don't change this value! (If you want a different storage-type, check out the other example-configs)
|
||||
storage-type: SQL
|
||||
storage-type: sql
|
||||
|
||||
# The JDBC-Connection URL that is used to connect to the database.
|
||||
# The format for this url is usually something like: jdbc:[driver]://[host]:[port]/[database]
|
||||
|
@ -39,7 +39,9 @@ max-connections: -1
|
|||
|
||||
# The compression-type that bluemap will use to compress generated map-data.
|
||||
# Available compression-types are:
|
||||
# - GZIP
|
||||
# - NONE
|
||||
# The default is: GZIP
|
||||
compression: GZIP
|
||||
# - gzip
|
||||
# - zstd
|
||||
# - deflate
|
||||
# - none
|
||||
# The default is: gzip
|
||||
compression: gzip
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
"vue-i18n": "^9.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.0.5",
|
||||
"@vitejs/plugin-vue": "^4.5.3",
|
||||
"eslint": "~8.22.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"sass": "^1.57.0",
|
||||
"vite": "^4.0.5"
|
||||
"vite": "^4.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
|
@ -34,9 +34,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
|
||||
"integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
@ -50,9 +50,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -66,9 +66,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -82,9 +82,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -98,9 +98,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -114,9 +114,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -130,9 +130,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -146,9 +146,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
|
||||
"integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
@ -162,9 +162,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -178,9 +178,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
|
||||
"integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
@ -194,9 +194,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
|
||||
"integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
|
||||
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
|
@ -210,9 +210,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
|
||||
"integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
|
||||
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
|
@ -226,9 +226,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
|
||||
"integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
|
||||
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
|
@ -242,9 +242,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
|
||||
"integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
|
||||
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
|
@ -258,9 +258,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
|
||||
"integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
|
||||
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
|
@ -274,9 +274,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -290,9 +290,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -306,9 +306,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -322,9 +322,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -338,9 +338,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -354,9 +354,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
|
||||
"integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
@ -370,9 +370,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -531,15 +531,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz",
|
||||
"integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==",
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
|
||||
"integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^4.0.0",
|
||||
"vite": "^4.0.0 || ^5.0.0",
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
|
@ -949,9 +949,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
|
||||
"integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
|
@ -961,28 +961,28 @@
|
|||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/android-arm": "0.17.19",
|
||||
"@esbuild/android-arm64": "0.17.19",
|
||||
"@esbuild/android-x64": "0.17.19",
|
||||
"@esbuild/darwin-arm64": "0.17.19",
|
||||
"@esbuild/darwin-x64": "0.17.19",
|
||||
"@esbuild/freebsd-arm64": "0.17.19",
|
||||
"@esbuild/freebsd-x64": "0.17.19",
|
||||
"@esbuild/linux-arm": "0.17.19",
|
||||
"@esbuild/linux-arm64": "0.17.19",
|
||||
"@esbuild/linux-ia32": "0.17.19",
|
||||
"@esbuild/linux-loong64": "0.17.19",
|
||||
"@esbuild/linux-mips64el": "0.17.19",
|
||||
"@esbuild/linux-ppc64": "0.17.19",
|
||||
"@esbuild/linux-riscv64": "0.17.19",
|
||||
"@esbuild/linux-s390x": "0.17.19",
|
||||
"@esbuild/linux-x64": "0.17.19",
|
||||
"@esbuild/netbsd-x64": "0.17.19",
|
||||
"@esbuild/openbsd-x64": "0.17.19",
|
||||
"@esbuild/sunos-x64": "0.17.19",
|
||||
"@esbuild/win32-arm64": "0.17.19",
|
||||
"@esbuild/win32-ia32": "0.17.19",
|
||||
"@esbuild/win32-x64": "0.17.19"
|
||||
"@esbuild/android-arm": "0.18.20",
|
||||
"@esbuild/android-arm64": "0.18.20",
|
||||
"@esbuild/android-x64": "0.18.20",
|
||||
"@esbuild/darwin-arm64": "0.18.20",
|
||||
"@esbuild/darwin-x64": "0.18.20",
|
||||
"@esbuild/freebsd-arm64": "0.18.20",
|
||||
"@esbuild/freebsd-x64": "0.18.20",
|
||||
"@esbuild/linux-arm": "0.18.20",
|
||||
"@esbuild/linux-arm64": "0.18.20",
|
||||
"@esbuild/linux-ia32": "0.18.20",
|
||||
"@esbuild/linux-loong64": "0.18.20",
|
||||
"@esbuild/linux-mips64el": "0.18.20",
|
||||
"@esbuild/linux-ppc64": "0.18.20",
|
||||
"@esbuild/linux-riscv64": "0.18.20",
|
||||
"@esbuild/linux-s390x": "0.18.20",
|
||||
"@esbuild/linux-x64": "0.18.20",
|
||||
"@esbuild/netbsd-x64": "0.18.20",
|
||||
"@esbuild/openbsd-x64": "0.18.20",
|
||||
"@esbuild/sunos-x64": "0.18.20",
|
||||
"@esbuild/win32-arm64": "0.18.20",
|
||||
"@esbuild/win32-ia32": "0.18.20",
|
||||
"@esbuild/win32-x64": "0.18.20"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
|
@ -1651,9 +1651,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
@ -1816,9 +1816,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.24",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
|
||||
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
|
||||
"version": "8.4.33",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -1834,7 +1834,7 @@
|
|||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
|
@ -1952,9 +1952,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.24.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.24.0.tgz",
|
||||
"integrity": "sha512-OgraHOIg2YpHQTjl0/ymWfFNBEyPucB7lmhXrQUh38qNOegxLapSPFs9sNr0qKR75awW41D93XafoR2QfhBdUQ==",
|
||||
"version": "3.29.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
||||
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
|
@ -2008,9 +2008,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
|
@ -2179,14 +2179,14 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.3.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
|
||||
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.17.5",
|
||||
"postcss": "^8.4.23",
|
||||
"rollup": "^3.21.0"
|
||||
"esbuild": "^0.18.10",
|
||||
"postcss": "^8.4.27",
|
||||
"rollup": "^3.27.1"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
|
@ -2194,12 +2194,16 @@
|
|||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 14",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
|
@ -2212,6 +2216,9 @@
|
|||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
|
@ -2295,9 +2302,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
@ -2344,156 +2351,156 @@
|
|||
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA=="
|
||||
},
|
||||
"@esbuild/android-arm": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
|
||||
"integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/android-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/android-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/darwin-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/darwin-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/freebsd-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/freebsd-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-arm": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
|
||||
"integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-ia32": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
|
||||
"integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-loong64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
|
||||
"integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
|
||||
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-mips64el": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
|
||||
"integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
|
||||
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-ppc64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
|
||||
"integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
|
||||
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-riscv64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
|
||||
"integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
|
||||
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-s390x": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
|
||||
"integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
|
||||
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/netbsd-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/openbsd-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/sunos-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-arm64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
|
||||
"integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-ia32": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
|
||||
"integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-x64": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
|
||||
"integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
|
@ -2606,9 +2613,9 @@
|
|||
}
|
||||
},
|
||||
"@vitejs/plugin-vue": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz",
|
||||
"integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==",
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
|
||||
"integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
|
@ -2935,33 +2942,33 @@
|
|||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
|
||||
"integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@esbuild/android-arm": "0.17.19",
|
||||
"@esbuild/android-arm64": "0.17.19",
|
||||
"@esbuild/android-x64": "0.17.19",
|
||||
"@esbuild/darwin-arm64": "0.17.19",
|
||||
"@esbuild/darwin-x64": "0.17.19",
|
||||
"@esbuild/freebsd-arm64": "0.17.19",
|
||||
"@esbuild/freebsd-x64": "0.17.19",
|
||||
"@esbuild/linux-arm": "0.17.19",
|
||||
"@esbuild/linux-arm64": "0.17.19",
|
||||
"@esbuild/linux-ia32": "0.17.19",
|
||||
"@esbuild/linux-loong64": "0.17.19",
|
||||
"@esbuild/linux-mips64el": "0.17.19",
|
||||
"@esbuild/linux-ppc64": "0.17.19",
|
||||
"@esbuild/linux-riscv64": "0.17.19",
|
||||
"@esbuild/linux-s390x": "0.17.19",
|
||||
"@esbuild/linux-x64": "0.17.19",
|
||||
"@esbuild/netbsd-x64": "0.17.19",
|
||||
"@esbuild/openbsd-x64": "0.17.19",
|
||||
"@esbuild/sunos-x64": "0.17.19",
|
||||
"@esbuild/win32-arm64": "0.17.19",
|
||||
"@esbuild/win32-ia32": "0.17.19",
|
||||
"@esbuild/win32-x64": "0.17.19"
|
||||
"@esbuild/android-arm": "0.18.20",
|
||||
"@esbuild/android-arm64": "0.18.20",
|
||||
"@esbuild/android-x64": "0.18.20",
|
||||
"@esbuild/darwin-arm64": "0.18.20",
|
||||
"@esbuild/darwin-x64": "0.18.20",
|
||||
"@esbuild/freebsd-arm64": "0.18.20",
|
||||
"@esbuild/freebsd-x64": "0.18.20",
|
||||
"@esbuild/linux-arm": "0.18.20",
|
||||
"@esbuild/linux-arm64": "0.18.20",
|
||||
"@esbuild/linux-ia32": "0.18.20",
|
||||
"@esbuild/linux-loong64": "0.18.20",
|
||||
"@esbuild/linux-mips64el": "0.18.20",
|
||||
"@esbuild/linux-ppc64": "0.18.20",
|
||||
"@esbuild/linux-riscv64": "0.18.20",
|
||||
"@esbuild/linux-s390x": "0.18.20",
|
||||
"@esbuild/linux-x64": "0.18.20",
|
||||
"@esbuild/netbsd-x64": "0.18.20",
|
||||
"@esbuild/openbsd-x64": "0.18.20",
|
||||
"@esbuild/sunos-x64": "0.18.20",
|
||||
"@esbuild/win32-arm64": "0.18.20",
|
||||
"@esbuild/win32-ia32": "0.18.20",
|
||||
"@esbuild/win32-x64": "0.18.20"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
|
@ -3474,9 +3481,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
|
@ -3585,11 +3592,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.24",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
|
||||
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
|
||||
"version": "8.4.33",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||
"requires": {
|
||||
"nanoid": "^3.3.6",
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
|
@ -3659,9 +3666,9 @@
|
|||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "3.24.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.24.0.tgz",
|
||||
"integrity": "sha512-OgraHOIg2YpHQTjl0/ymWfFNBEyPucB7lmhXrQUh38qNOegxLapSPFs9sNr0qKR75awW41D93XafoR2QfhBdUQ==",
|
||||
"version": "3.29.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
||||
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
|
@ -3688,9 +3695,9 @@
|
|||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
|
@ -3813,15 +3820,15 @@
|
|||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.3.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
|
||||
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.17.5",
|
||||
"esbuild": "^0.18.10",
|
||||
"fsevents": "~2.3.2",
|
||||
"postcss": "^8.4.23",
|
||||
"rollup": "^3.21.0"
|
||||
"postcss": "^8.4.27",
|
||||
"rollup": "^3.27.1"
|
||||
}
|
||||
},
|
||||
"vue": {
|
||||
|
@ -3872,9 +3879,9 @@
|
|||
}
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
"hocon-parser": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.0.5",
|
||||
"@vitejs/plugin-vue": "^4.5.3",
|
||||
"eslint": "~8.22.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"sass": "^1.57.0",
|
||||
"vite": "^4.0.5"
|
||||
"vite": "^4.5.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,9 @@
|
|||
light: "Light"
|
||||
contrast: "Contrast"
|
||||
}
|
||||
chunkBorders: {
|
||||
button: "Show chunk borders"
|
||||
}
|
||||
debug: {
|
||||
button: "Debug"
|
||||
}
|
||||
|
|
|
@ -83,6 +83,9 @@
|
|||
light: "Vaalea"
|
||||
contrast: "Kontrasti"
|
||||
}
|
||||
chunkBorders: {
|
||||
button: "Näytä lohkojen rajat"
|
||||
}
|
||||
debug: {
|
||||
button: "Debug"
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
title: "Balises"
|
||||
button: "Balises"
|
||||
tooltip: "Liste des balises"
|
||||
marker: "balises | balises"
|
||||
marker: "balise | balises"
|
||||
markerSet: "Collection de balises | Collections de balises"
|
||||
searchPlaceholder: "Rechercher..."
|
||||
followPlayerTitle: "Suivre ce Joueur"
|
||||
|
@ -51,7 +51,7 @@
|
|||
dayNightSwitch: {
|
||||
tooltip: "Jour/Nuit"
|
||||
}
|
||||
sunlight: "Soleil"
|
||||
sunlight: "Lumière du Soleil"
|
||||
ambientLight: "Lumière Ambiante"
|
||||
}
|
||||
resolution: {
|
||||
|
@ -70,9 +70,9 @@
|
|||
invertMouseY: "Inverser l\'Y de la Souris"
|
||||
}
|
||||
renderDistance: {
|
||||
title: "Distance de rendu"
|
||||
hiresLayer: "Couche haute définition"
|
||||
lowersLayer: "Couche basse définition"
|
||||
title: "Distance de Rendu"
|
||||
hiresLayer: "Couche Haute Définition"
|
||||
lowersLayer: "Couche Basse Définition"
|
||||
loadHiresWhileMoving: "Charger la haute définition en bougeant"
|
||||
off: "Désactivé"
|
||||
}
|
||||
|
@ -84,7 +84,7 @@
|
|||
contrast: "Contrasté"
|
||||
}
|
||||
debug: {
|
||||
button: "Debug"
|
||||
button: "Debogage"
|
||||
}
|
||||
resetAllSettings: {
|
||||
button: "Réinitialiser tous les Paramètres"
|
||||
|
@ -94,7 +94,7 @@
|
|||
tooltip: "Liste des Joueurs"
|
||||
}
|
||||
compass: {
|
||||
tooltip: "Boussole / Pointe le Nord"
|
||||
tooltip: "Boussole / Pointer le Nord"
|
||||
}
|
||||
screenshot: {
|
||||
title: "Capture d\'écran"
|
||||
|
@ -122,7 +122,7 @@
|
|||
blockTooltip: {
|
||||
block: "Bloc"
|
||||
position: "Position"
|
||||
chunk: "Chunk"
|
||||
chunk: "Tronçon"
|
||||
region: {
|
||||
region: "Région"
|
||||
file: "Fichier"
|
||||
|
@ -141,25 +141,25 @@
|
|||
<p>
|
||||
<h2>Contrôles de la Souris :</h2>
|
||||
<table>
|
||||
<tr><th>déplacement</th><td><kbd>clic-gauche</kbd> + mouvement</td></tr>
|
||||
<tr><th>zoom</th><td><kbd>molette</kbd> (scroll)</td></tr>
|
||||
<tr><th>rotation / inclinaison</th><td><kbd>clic-droit</kbd> + mouvement</td></tr>
|
||||
<tr><th>déplacer</th><td><kbd>clic-gauche</kbd> + glisser</td></tr>
|
||||
<tr><th>zoomer</th><td><kbd>molette</kbd> (défiler)</td></tr>
|
||||
<tr><th>tourner / incliner</th><td><kbd>clic-droit</kbd> + glisser</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
<h2>Contrôles du Clavier :</h2>
|
||||
<table>
|
||||
<tr><th>déplacement</th><td><kbd>zqsd</kbd> / <kbd>flèches</kbd></td></tr>
|
||||
<tr><th>zoom</th><td>Pavé Numérique : <kbd>+</kbd>/<kbd>-</kbd> ou <kbd>Inser</kbd>/<kbd>Début</kbd></td></tr>
|
||||
<tr><th>rotation / inclinaison</th><td><kbd>Alt-Gauche</kbd> + <kbd>zqsd</kbd> / <kbd>flèches</kbd> ou <kbd>Suppr</kbd>/<kbd>Fin</kbd>/<kbd>Page Up</kbd>/<kbd>Page Down</kbd></td></tr>
|
||||
<tr><th>déplacer</th><td><kbd>zqsd</kbd> / <kbd>flèches directionnelles</kbd></td></tr>
|
||||
<tr><th>zoomer</th><td>Pavé Numérique : <kbd>+</kbd>/<kbd>-</kbd> ou <kbd>Inser</kbd>/<kbd>Début</kbd></td></tr>
|
||||
<tr><th>tourner / incliner</th><td><kbd>Alt-Gauche</kbd> + <kbd>zqsd</kbd> / <kbd>flèches</kbd> ou <kbd>Suppr</kbd>/<kbd>Fin</kbd>/<kbd>Page Haut</kbd>/<kbd>Page Bas</kbd></td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
<h2>Contrôles du Toucher :</h2>
|
||||
<h2>Contrôles Tactiles :</h2>
|
||||
<table>
|
||||
<tr><th>déplacement</th><td>toucher + mouvement</td></tr>
|
||||
<tr><th>zoom</th><td>toucher avec 2 doigts + pincement</td></tr>
|
||||
<tr><th>rotation / inclinaison</th><td>toucher avec 2 doigts + rotation / haut/bas</td></tr>
|
||||
<tr><th>déplacer</th><td>toucher + glisser</td></tr>
|
||||
<tr><th>zoomer</th><td>toucher avec 2 doigts + pincement</td></tr>
|
||||
<tr><th>tourner / incliner</th><td>toucher avec 2 doigts + tourner / haut/bas</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<br><hr>
|
||||
|
|
|
@ -83,6 +83,9 @@
|
|||
light: "Licht"
|
||||
contrast: "Contrast"
|
||||
}
|
||||
chunkBorders: {
|
||||
button: "Laat chunk grenzen zien"
|
||||
}
|
||||
debug: {
|
||||
button: "Debug"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
title: "Меню"
|
||||
tooltip: "Меню"
|
||||
}
|
||||
map: {
|
||||
unloaded: "Карта не загружена."
|
||||
loading: "Карта загружается..."
|
||||
errored: "При загрузке этой карты произошла ошибка!"
|
||||
}
|
||||
maps: {
|
||||
title: "Карта"
|
||||
button: "Карта"
|
||||
|
@ -17,6 +22,14 @@
|
|||
markerSet: "набор маркеров | наборы маркеров"
|
||||
searchPlaceholder: "Поиск..."
|
||||
followPlayerTitle: "Следовать за игроком"
|
||||
sort {
|
||||
title: "Сортировать по"
|
||||
by {
|
||||
default: "умолчанию"
|
||||
label: "имени"
|
||||
distance: "расстоянию"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings: {
|
||||
title: "Настройки"
|
||||
|
@ -27,7 +40,7 @@
|
|||
}
|
||||
resetCamera: {
|
||||
button: "Сброс настроек камеры"
|
||||
tooltip: "Сбросить настройки и положение камеры"
|
||||
tooltip: "Сброс настроек и положения камеры"
|
||||
}
|
||||
updateMap: {
|
||||
button: "Обновить карту"
|
||||
|
@ -54,7 +67,7 @@
|
|||
freeFlightControls: {
|
||||
title: "Управление свободным полётом"
|
||||
mouseSensitivity: "Чувствительность мыши"
|
||||
invertMouseY: "Инвертировать мышь по Y"
|
||||
invertMouseY: "Инвертировать мышь по оси Y"
|
||||
}
|
||||
renderDistance: {
|
||||
title: "Дальность прорисовки"
|
||||
|
@ -70,6 +83,9 @@
|
|||
light: "Светлая"
|
||||
contrast: "Контрастная"
|
||||
}
|
||||
chunkBorders: {
|
||||
button: "Показывать границы чанков"
|
||||
}
|
||||
debug: {
|
||||
button: "Отладка"
|
||||
}
|
||||
|
@ -116,8 +132,8 @@
|
|||
}
|
||||
light: {
|
||||
light: "Освещение"
|
||||
sun: "Неба"
|
||||
block: "Блоками"
|
||||
sun: "Небо"
|
||||
block: "Блоки"
|
||||
}
|
||||
}
|
||||
info: {
|
||||
|
@ -129,7 +145,7 @@
|
|||
<h2>Управление мышью:</h2>
|
||||
<table>
|
||||
<tr><th>перемещение</th><td>зажать <kbd>ЛКМ</kbd></td></tr>
|
||||
<tr><th>приближение</th><td>прокрутить <kbd>колесо</kbd></td></tr>
|
||||
<tr><th>приближение</th><td>прокрутить <kbd>колёсико</kbd></td></tr>
|
||||
<tr><th>поворот / наклон</th><td>зажать <kbd>ПКМ</kbd></td></tr>
|
||||
</table>
|
||||
</p>
|
||||
|
|
|
@ -5,23 +5,23 @@
|
|||
tooltip: "Menü"
|
||||
}
|
||||
map: {
|
||||
unloaded: "Herhangi harita yüklenmedi."
|
||||
unloaded: "Haritalar yüklenmedi."
|
||||
loading: "Harita yükleniyor..."
|
||||
errored: "Bu haritayı yüklerken bir hata oluştu!"
|
||||
errored: "Haritayı yüklerken bir hata oluştu!"
|
||||
}
|
||||
maps: {
|
||||
title: "Haritalar"
|
||||
button: "Haritalar"
|
||||
tooltip: "Harita-Listesi"
|
||||
tooltip: "Harita listesi"
|
||||
}
|
||||
markers: {
|
||||
title: "Noktalar"
|
||||
button: "Noktalar"
|
||||
tooltip: "Nokta-Listesi"
|
||||
tooltip: "Nokta listesi"
|
||||
marker: "nokta | noktalar"
|
||||
markerSet: "nokta-kur | noktalar-kur"
|
||||
markerSet: "nokta kur | noktalar kur"
|
||||
searchPlaceholder: "Ara..."
|
||||
followPlayerTitle: "Oyuncuyu Takip Et"
|
||||
followPlayerTitle: "Oyuncuyu takip et"
|
||||
sort {
|
||||
title: "Sıralama türü"
|
||||
by {
|
||||
|
@ -36,23 +36,23 @@
|
|||
button: "Ayarlar"
|
||||
}
|
||||
goFullscreen: {
|
||||
button: "Tam Ekran"
|
||||
button: "Tam ekran"
|
||||
}
|
||||
resetCamera: {
|
||||
button: "Kamerayı Sıfırla"
|
||||
tooltip: "Kamerayı & Pozisyonu Sıfırla"
|
||||
button: "Kamerayı sıfırla"
|
||||
tooltip: "Kamerayı ve pozisyonu sıfırla"
|
||||
}
|
||||
updateMap: {
|
||||
button: "Haritayı Güncele"
|
||||
tooltip: "Harita Parça Önbelleğini Temizle"
|
||||
button: "Haritayı güncele"
|
||||
tooltip: "Harita Parça önbelleğini temizle"
|
||||
}
|
||||
lighting: {
|
||||
title: "Aydınlatma"
|
||||
dayNightSwitch: {
|
||||
tooltip: "Sabah/Akşam"
|
||||
}
|
||||
sunlight: "Güneş Işığı"
|
||||
ambientLight: "Çevre-Işığı"
|
||||
sunlight: "Güneş ışığı"
|
||||
ambientLight: "Çevre ışığı"
|
||||
}
|
||||
resolution: {
|
||||
title: "Çözünürlük"
|
||||
|
@ -61,20 +61,20 @@
|
|||
low: "Düşük (Upscaling x0.5)"
|
||||
}
|
||||
mapControls: {
|
||||
title: "Harita Kontrolleri"
|
||||
showZoomButtons: "Yakınlaştırma Tuşlarını Göster"
|
||||
title: "Harita kontrolleri"
|
||||
showZoomButtons: "Yakınlaştırma tuşlarını göster"
|
||||
}
|
||||
freeFlightControls: {
|
||||
title: "Serbest Kamera Kontrolleri"
|
||||
mouseSensitivity: "Fare-Hassasiyeti"
|
||||
invertMouseY: "Fareyi Tersine Çevir"
|
||||
title: "Serbest kamera kontrolleri"
|
||||
mouseSensitivity: "Fare hassasiyeti"
|
||||
invertMouseY: "Fareyi tersine çevir"
|
||||
}
|
||||
renderDistance: {
|
||||
title: "Görüş Mesafesi"
|
||||
hiresLayer: "Hires Katmanı"
|
||||
lowersLayer: "Lowres Katmanı"
|
||||
loadHiresWhileMoving: "Haraket ederken hires'i yüklemeye devam et"
|
||||
off: "Kapalıl"
|
||||
title: "Görüş mesafesi"
|
||||
hiresLayer: "Yüksek çözünürlük katmanı"
|
||||
lowersLayer: "Düşük çözünürlük Katmanı"
|
||||
loadHiresWhileMoving: "Haraket ettirirken yüklemeye devam et"
|
||||
off: "Kapalı"
|
||||
}
|
||||
theme: {
|
||||
title: "Tema"
|
||||
|
@ -84,36 +84,36 @@
|
|||
contrast: "Kontrast"
|
||||
}
|
||||
debug: {
|
||||
button: "Hata Ayıkla"
|
||||
button: "Hata ayıklama"
|
||||
}
|
||||
resetAllSettings: {
|
||||
button: "Tüm Ayarları Sıfırla"
|
||||
button: "Tüm ayarları sıfırla"
|
||||
}
|
||||
players: {
|
||||
title: "Oyuncular"
|
||||
tooltip: "Oyuncu-Listesi"
|
||||
tooltip: "Oyuncu listesi"
|
||||
}
|
||||
compass: {
|
||||
tooltip: "Pusula / Kuzeye Dön"
|
||||
}
|
||||
screenshot: {
|
||||
title: "Ekran Görüntüsü"
|
||||
button: "Ekran Görüntüsü Al"
|
||||
clipboard: "Panoya Kaydet"
|
||||
title: "Ekran görüntüsü"
|
||||
button: "Ekran görüntüsü Al"
|
||||
clipboard: "Panoya kaydet"
|
||||
}
|
||||
controls: {
|
||||
title: "Görüş / Kontroller"
|
||||
perspective: {
|
||||
button: "Perspektif"
|
||||
tooltip: "Perspektif-Bakış"
|
||||
tooltip: "Perspektif bakışı"
|
||||
}
|
||||
flatView: {
|
||||
button: "Düz"
|
||||
tooltip: "Ortografik / Düz-Bakış"
|
||||
button: "Kuşbakışı"
|
||||
tooltip: "Ortografik / Kuşbakışı"
|
||||
}
|
||||
freeFlight: {
|
||||
button: "Serbest-Kamera"
|
||||
tooltip: "Serbest-Kamera / İzleyici Modu"
|
||||
button: "Serbest kamera"
|
||||
tooltip: "Serbest kamera / Seyirci modu"
|
||||
}
|
||||
}
|
||||
language: {
|
||||
|
@ -139,32 +139,32 @@
|
|||
content: """
|
||||
<img src="assets/logo.png" style="display: block; width: 40%; margin: 3em auto; border-radius: 50%">
|
||||
<p>
|
||||
<h2>Fare-Kontrolleri:</h2>
|
||||
<h2>Fare Kontrolleri:</h2>
|
||||
<table>
|
||||
<tr><th>taşı</th><td><kbd>sol tık</kbd> + haraket ettir</td></tr>
|
||||
<tr><th>yakınlaştır</th><td><kbd>fare tekerleği</kbd> (kaydır)</td></tr>
|
||||
<tr><th>döndür / eğilt</th><td><kbd>sağ tık</kbd> + haraket ettir</td></tr>
|
||||
<tr><th>Taşı</th><td><kbd>sol tık</kbd> + haraket ettir</td></tr>
|
||||
<tr><th>Yakınlaştır</th><td><kbd>fare tekerleği</kbd> (kaydır)</td></tr>
|
||||
<tr><th>Döndür / Eğilt</th><td><kbd>sağ tık</kbd> + haraket ettir</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
<h2>Klavye-Kontrolleri:</h2>
|
||||
<h2>Klavye Kontrolleri:</h2>
|
||||
<table>
|
||||
<tr><th>taşı</th><td><kbd>wasd</kbd> / <kbd>ok tuşları</kbd></td></tr>
|
||||
<tr><th>yakınlaştır</th><td>Numpad: <kbd>+</kbd>/<kbd>-</kbd> veya <kbd>Ins</kbd>/<kbd>Home</kbd></td></tr>
|
||||
<tr><th>döndür / eğilt</th><td><kbd>Left-Alt</kbd> + <kbd>wasd</kbd> / <kbd>arrow-keys</kbd> veya <kbd>Delete</kbd>/<kbd>End</kbd>/<kbd>Page Up</kbd>/<kbd>Page Down</kbd></td></tr>
|
||||
<tr><th>Taşı</th><td><kbd>wasd</kbd> / <kbd>ok tuşları</kbd></td></tr>
|
||||
<tr><th>Yakınlaştır</th><td>Numpad: <kbd>+</kbd>/<kbd>-</kbd> veya <kbd>Ins</kbd>/<kbd>Home</kbd></td></tr>
|
||||
<tr><th>Döndür / Eğim ver</th><td><kbd>Left-Alt</kbd> + <kbd>wasd</kbd> / <kbd>ok tuşları</kbd> veya <kbd>Delete</kbd>/<kbd>End</kbd>/<kbd>Page Up</kbd>/<kbd>Page Down</kbd></td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
<h2>Dokunmatik-Kontrolleri:</h2>
|
||||
<h2>Dokunmatik Kontrolleri:</h2>
|
||||
<table>
|
||||
<tr><th>taşı</th><td>dokun + kaydır</td></tr>
|
||||
<tr><th>yakınlaştır</th><td>iki parmağını birbirine yaklaştır/uzaklaştır</td></tr>
|
||||
<tr><th>döndür / eğilt</th><td>iki parmakla dokun + haraket ettir / aşşağı/yukarı kaydır</td></tr>
|
||||
<tr><th>Taşı</th><td>dokun + kaydır</td></tr>
|
||||
<tr><th>Yakınlaştır</th><td>iki parmaklarını birbirine yaklaştır/uzaklaştır</td></tr>
|
||||
<tr><th>Döndür / Eğim ver</th><td>iki parmağınla dokun + haraket ettir / aşağı/yukarı kaydır</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<br><hr>
|
||||
<p class="info-footer">
|
||||
Bu harita ♥ using <a href="https://bluecolo.red/bluemap">BlueMap</a> {version} ile oluşturulmuştur
|
||||
Bu harita ♥ <a href="https://bluecolo.red/bluemap">BlueMap</a> {version} ile oluşturulmuştur
|
||||
</p>
|
||||
"""
|
||||
}
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
title: "Меню"
|
||||
tooltip: "Меню"
|
||||
}
|
||||
map: {
|
||||
unloaded: "Карта не завантажена."
|
||||
loading: "Карта завантажується..."
|
||||
errored: "При завантаженні цієї карти сталася помилка!"
|
||||
}
|
||||
maps: {
|
||||
title: "Карти"
|
||||
button: "Карти"
|
||||
|
@ -17,6 +22,14 @@
|
|||
markerSet: "набір маркерів | набори маркерів"
|
||||
searchPlaceholder: "Пошук..."
|
||||
followPlayerTitle: "Слідкувати за гравцем"
|
||||
sort {
|
||||
title: "Сортувати за"
|
||||
by {
|
||||
default: "замовчуванням"
|
||||
label: "імені"
|
||||
distance: "відстані"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings: {
|
||||
title: "Налаштування"
|
||||
|
@ -26,8 +39,8 @@
|
|||
button: "Перейти в повноекранний режим"
|
||||
}
|
||||
resetCamera: {
|
||||
button: "Скинути камеру"
|
||||
tooltip: "Скинути камеру та позицію"
|
||||
button: "Скинути налаштування камери"
|
||||
tooltip: "Скинути налаштування та позицію камери"
|
||||
}
|
||||
updateMap: {
|
||||
button: "Оновити карту"
|
||||
|
@ -70,6 +83,9 @@
|
|||
light: "Світла"
|
||||
contrast: "Контрастна"
|
||||
}
|
||||
chunkBorders: {
|
||||
button: "Показувати межі чанків"
|
||||
}
|
||||
debug: {
|
||||
button: "Відлагоджувальний режим"
|
||||
}
|
||||
|
@ -117,7 +133,7 @@
|
|||
light: {
|
||||
light: "Освітлення"
|
||||
sun: "Сонце"
|
||||
block: "Блок"
|
||||
block: "Блоки"
|
||||
}
|
||||
}
|
||||
info: {
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
title: "菜单"
|
||||
tooltip: "打开菜单"
|
||||
}
|
||||
map: {
|
||||
unloaded: "未加载地图."
|
||||
loading: "正在加载地图..."
|
||||
errored: "尝试加载此地图时出错!"
|
||||
}
|
||||
maps: {
|
||||
title: "地图"
|
||||
button: "地图"
|
||||
|
@ -17,6 +22,14 @@
|
|||
markerSet: "标记集 | 标记集"
|
||||
searchPlaceholder: "搜索..."
|
||||
followPlayerTitle: "跟随玩家"
|
||||
sort {
|
||||
title: "排序方式"
|
||||
by {
|
||||
default: "默认"
|
||||
label: "名称"
|
||||
distance: "距离"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings: {
|
||||
title: "设置"
|
||||
|
@ -27,7 +40,7 @@
|
|||
}
|
||||
resetCamera: {
|
||||
button: "重置镜头"
|
||||
tooltip: "重置镜头 & 位置"
|
||||
tooltip: "重置镜头和位置"
|
||||
}
|
||||
updateMap: {
|
||||
button: "更新地图"
|
||||
|
@ -47,15 +60,21 @@
|
|||
normal: "普通(默认 x1)"
|
||||
low: "低(粗糙 x0.5)"
|
||||
}
|
||||
mapControls: {
|
||||
title: "地图控制"
|
||||
showZoomButtons: "显示缩放按钮"
|
||||
}
|
||||
freeFlightControls: {
|
||||
title: "自由飞行控制"
|
||||
mouseSensitivity: "鼠标灵敏度"
|
||||
invertMouseY: "反转鼠标 Y"
|
||||
invertMouseY: "反转鼠标 Y 轴"
|
||||
}
|
||||
renderDistance: {
|
||||
title: "渲染距离"
|
||||
hiresLayer: "高分辨率"
|
||||
lowersLayer: "低分辨率"
|
||||
loadHiresWhileMoving: "移动时加载高清图像"
|
||||
off: "关闭"
|
||||
}
|
||||
theme: {
|
||||
title: "主题"
|
||||
|
@ -75,7 +94,7 @@
|
|||
tooltip: "玩家列表"
|
||||
}
|
||||
compass: {
|
||||
tooltip: "罗盘 / 朝北"
|
||||
tooltip: "指南针 / 朝北"
|
||||
}
|
||||
screenshot: {
|
||||
title: "截图"
|
||||
|
@ -122,9 +141,9 @@
|
|||
<p>
|
||||
<h2>鼠标控制:</h2>
|
||||
<table>
|
||||
<tr><th>移动</th><td><kbd>左键</kbd> + 拖拽</td></tr>
|
||||
<tr><th>缩放</th><td><kbd>鼠标中键</kbd> (滚动)</td></tr>
|
||||
<tr><th>旋转 / 倾斜</th><td><kbd>右键</kbd> + 拖拽</td></tr>
|
||||
<tr><th>移动</th><td><kbd>左键点击</kbd> + 拖动</td></tr>
|
||||
<tr><th>缩放</th><td><kbd>鼠标滚轮</kbd>(滚动)</td></tr>
|
||||
<tr><th>旋转 / 倾斜</th><td><kbd>右键点击</kbd> + 拖动</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -138,8 +157,8 @@
|
|||
<p>
|
||||
<h2>触屏控制:</h2>
|
||||
<table>
|
||||
<tr><th>移动</th><td>触屏 + 拖拽</td></tr>
|
||||
<tr><th>缩放</th><td>双指触屏 + 滑动</td></tr>
|
||||
<tr><th>移动</th><td>触屏 + 拖动</td></tr>
|
||||
<tr><th>缩放</th><td>双指触屏 + 捏合</td></tr>
|
||||
<tr><th>旋转 / 倾斜</th><td>双指触屏 + 旋转 / 向上滑动 / 向下滑动</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
|
|
|
@ -4,17 +4,32 @@
|
|||
title: "選單"
|
||||
tooltip: "選單"
|
||||
}
|
||||
map: {
|
||||
unloaded: "未載入任何地圖。"
|
||||
loading: "載入地圖中..."
|
||||
errored: "載入地圖時發生錯誤!"
|
||||
}
|
||||
maps: {
|
||||
title: "地圖"
|
||||
button: "地圖"
|
||||
tooltip: "地圖列表"
|
||||
}
|
||||
markers: {
|
||||
title: "圖釘"
|
||||
button: "圖釘"
|
||||
tooltip: "圖釘列表"
|
||||
marker: "圖釘 | 圖釘"
|
||||
markerSet: "圖釘設定 | 圖釘設定"
|
||||
title: "標記"
|
||||
button: "標記"
|
||||
tooltip: "標記列表"
|
||||
marker: "標記 | 標記"
|
||||
markerSet: "標記組 | 標記組"
|
||||
searchPlaceholder: "搜尋..."
|
||||
followPlayerTitle: "跟隨玩家"
|
||||
sort: {
|
||||
title: "排序方式"
|
||||
by: {
|
||||
default: "預設"
|
||||
label: "名稱"
|
||||
distance: "距離"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings: {
|
||||
title: "設定"
|
||||
|
@ -24,69 +39,81 @@
|
|||
button: "全螢幕模式"
|
||||
}
|
||||
resetCamera: {
|
||||
button: "重置視角"
|
||||
tooltip: "重置視角 & 位置"
|
||||
button: "重設視角"
|
||||
tooltip: "重設視角與位置"
|
||||
}
|
||||
updateMap: {
|
||||
button: "更新地圖"
|
||||
tooltip: "清除暫存"
|
||||
}
|
||||
lighting: {
|
||||
title: "亮度"
|
||||
title: "光照"
|
||||
dayNightSwitch: {
|
||||
tooltip: "白天/夜晚"
|
||||
tooltip: "白天/黑夜模式"
|
||||
}
|
||||
sunlight: "時間"
|
||||
sunlight: "陽光"
|
||||
ambientLight: "環境光"
|
||||
}
|
||||
resolution: {
|
||||
title: "解析度"
|
||||
high: "高 (最高畫質SSAA x2)"
|
||||
normal: "預設 (畫質優先Native x1)"
|
||||
low: "低 (效能優先Upscaling x0.5)"
|
||||
high: "高 (SSAA反鋸齒 x2)"
|
||||
normal: "正常 (原生 x1)"
|
||||
low: "低 (縮放 x0.5)"
|
||||
}
|
||||
mapControls: {
|
||||
title: "地圖控制"
|
||||
showZoomButtons: "顯示縮放按鈕"
|
||||
}
|
||||
freeFlightControls: {
|
||||
title: "滑鼠設定"
|
||||
title: "自由飛行控制"
|
||||
mouseSensitivity: "滑鼠靈敏度"
|
||||
invertMouseY: "反轉Y軸"
|
||||
invertMouseY: "反轉滑鼠Y軸"
|
||||
}
|
||||
renderDistance: {
|
||||
title: "顯示範圍"
|
||||
hiresLayer: "高畫質的距離"
|
||||
lowersLayer: "低畫質的距離"
|
||||
title: "渲染距離"
|
||||
hiresLayer: "高解析度層"
|
||||
lowersLayer: "低解析度層"
|
||||
loadHiresWhileMoving: "移動時加載高解析度層"
|
||||
off: "關閉"
|
||||
}
|
||||
theme: {
|
||||
title: "主題"
|
||||
default: "預設 (系統/瀏覽器)"
|
||||
dark: "黑暗"
|
||||
light: "明亮"
|
||||
dark: "深色模式"
|
||||
light: "明亮模式"
|
||||
contrast: "對比模式"
|
||||
}
|
||||
debug: {
|
||||
button: "除錯"
|
||||
button: "除錯模式"
|
||||
}
|
||||
resetAllSettings: {
|
||||
button: "重設所有設定"
|
||||
button: "重置所有設定"
|
||||
}
|
||||
players: {
|
||||
title: "玩家"
|
||||
tooltip: "玩家列表"
|
||||
}
|
||||
compass: {
|
||||
tooltip: "指北針"
|
||||
tooltip: "指南針 / 面向北方"
|
||||
}
|
||||
screenshot: {
|
||||
title: "截圖"
|
||||
button: "截圖"
|
||||
clipboard: "複製到剪貼板"
|
||||
}
|
||||
controls: {
|
||||
title: "顯示/控制"
|
||||
title: "視圖 / 控制"
|
||||
perspective: {
|
||||
button: "立體"
|
||||
tooltip: "立體顯示"
|
||||
button: "透視圖"
|
||||
tooltip: "透視視圖"
|
||||
}
|
||||
flatView: {
|
||||
button: "平面"
|
||||
tooltip: "正射/平面顯示"
|
||||
button: "平面視圖"
|
||||
tooltip: "正交視圖 / 平面視圖"
|
||||
}
|
||||
freeFlight: {
|
||||
button: "觀察者"
|
||||
tooltip: "觀察者模式"
|
||||
button: "自由飛行"
|
||||
tooltip: "自由飛行 / 旁觀模式"
|
||||
}
|
||||
}
|
||||
language: {
|
||||
|
@ -94,16 +121,16 @@
|
|||
}
|
||||
blockTooltip: {
|
||||
block: "方塊"
|
||||
position: "座標"
|
||||
position: "位置"
|
||||
chunk: "區塊"
|
||||
region: {
|
||||
region: "區域"
|
||||
file: "檔案"
|
||||
}
|
||||
light: {
|
||||
light: "亮度"
|
||||
light: "光線"
|
||||
sun: "日光"
|
||||
block: "光源"
|
||||
block: "方塊"
|
||||
}
|
||||
}
|
||||
info: {
|
||||
|
@ -112,32 +139,32 @@
|
|||
content: """
|
||||
<img src="assets/logo.png" style="display: block; width: 40%; margin: 3em auto; border-radius: 50%">
|
||||
<p>
|
||||
<h2>滑鼠控制:</h2>
|
||||
<h2>滑鼠控制:</h2>
|
||||
<table>
|
||||
<tr><th>移動:</th><td><kbd>左鍵</kbd>長按</td></tr>
|
||||
<tr><th>縮放:</th><td><kbd>滑鼠滾輪</kbd> (測試)</td></tr>
|
||||
<tr><th>旋轉/傾斜:</th><td><kbd>右鍵</kbd>長按</td></tr>
|
||||
<tr><th>移動</th><td><kbd>左鍵點擊</kbd> + 拖曳</td></tr>
|
||||
<tr><th>縮放</th><td><kbd>滑鼠滾輪</kbd> (滾動)</td></tr>
|
||||
<tr><th>旋轉 / 傾斜</th><td><kbd>右鍵點擊</kbd> + 拖曳</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
<h2>鍵盤控制:</h2>
|
||||
<h2>鍵盤控制:</h2>
|
||||
<table>
|
||||
<tr><th>移動:</th><td><kbd>點擊</kbd> / <kbd>方向鍵</kbd></td></tr>
|
||||
<tr><th>縮放:</th><td>Numpad: <kbd>+</kbd>/<kbd>-</kbd> or <kbd>Ins</kbd>/<kbd>Home</kbd></td></tr>
|
||||
<tr><th>旋轉/傾斜:</th><td><kbd>左Alt</kbd> + <kbd>點擊</kbd> / <kbd>方向鍵</kbd> 或 <kbd>Delete</kbd>/<kbd>End</kbd>/<kbd>Page Up</kbd>/<kbd>Page Down</kbd></td></tr>
|
||||
<tr><th>移動</th><td><kbd>wasd</kbd> / <kbd>方向鍵</kbd></td></tr>
|
||||
<tr><th>縮放</th><td>Numpad(數字鍵盤):<kbd>+</kbd>/<kbd>-</kbd> 或 <kbd>Ins</kbd>/<kbd>Home</kbd></td></tr>
|
||||
<tr><th>旋轉 / 傾斜</th><td><kbd>左Alt</kbd> + <kbd>wasd</kbd> / <kbd>方向鍵</kbd> 或 <kbd>Delete</kbd>/<kbd>End</kbd>/<kbd>Page Up</kbd>/<kbd>Page Down</kbd></td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
<h2>觸控:</h2>
|
||||
<h2>觸控控制:</h2>
|
||||
<table>
|
||||
<tr><th>移動:</th><td>拖動</td></tr>
|
||||
<tr><th>縮放:</th><td>用兩根手指觸摸</td></tr>
|
||||
<tr><th>旋轉/傾斜:</th><td>用兩根手指觸摸旋轉</td></tr>
|
||||
<tr><th>移動</th><td>觸控 + 拖曳</td></tr>
|
||||
<tr><th>縮放</th><td>用兩根手指觸碰旋轉</td></tr>
|
||||
<tr><th>旋轉/傾斜</th><td>用兩指觸控 + 旋轉 / 上下移動</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
<br><hr>
|
||||
<p class="info-footer">
|
||||
This map has been generated with ♥ using <a href="https://bluecolo.red/bluemap">BlueMap</a> {version}
|
||||
此地圖使用 ♥ 以 {version} 版本的 <a href="https://bluecolo.red/bluemap">BlueMap</a> 生成
|
||||
</p>
|
||||
"""
|
||||
}
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
<?php
|
||||
|
||||
// !!! SET YOUR SQL-CONNECTION SETTINGS HERE: !!!
|
||||
|
||||
$hostname = '127.0.0.1';
|
||||
$port = 3306;
|
||||
$username = 'root';
|
||||
$password = '';
|
||||
$database = 'bluemap';
|
||||
|
||||
// set this to "none" if you disabled compression on your maps
|
||||
$hiresCompression = 'gzip';
|
||||
|
||||
// !!! END - DONT CHANGE ANYTHING AFTER THIS LINE !!!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// some helper functions
|
||||
function error($code, $message = null) {
|
||||
global $path;
|
||||
|
||||
http_response_code($code);
|
||||
header("Content-Type: text/plain");
|
||||
echo "BlueMap php-script - $code\n";
|
||||
if ($message != null) echo $message."\n";
|
||||
echo "Requested Path: $path";
|
||||
exit;
|
||||
}
|
||||
|
||||
function startsWith($haystack, $needle) {
|
||||
return substr($haystack, 0, strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
// mime-types for meta-files
|
||||
$mimeDefault = "application/octet-stream";
|
||||
$mimeTypes = [
|
||||
"txt" => "text/plain",
|
||||
"css" => "text/css",
|
||||
"csv" => "text/csv",
|
||||
"htm" => "text/html",
|
||||
"html" => "text/html",
|
||||
"js" => "text/javascript",
|
||||
"xml" => "text/xml",
|
||||
|
||||
"png" => "image/png",
|
||||
"jpg" => "image/jpeg",
|
||||
"jpeg" => "image/jpeg",
|
||||
"gif" => "image/gif",
|
||||
"webp" => "image/webp",
|
||||
"tif" => "image/tiff",
|
||||
"tiff" => "image/tiff",
|
||||
"svg" => "image/svg+xml",
|
||||
|
||||
"json" => "application/json",
|
||||
|
||||
"mp3" => "audio/mpeg",
|
||||
"oga" => "audio/ogg",
|
||||
"wav" => "audio/wav",
|
||||
"weba" => "audio/webm",
|
||||
|
||||
"mp4" => "video/mp4",
|
||||
"mpeg" => "video/mpeg",
|
||||
"webm" => "video/webm",
|
||||
|
||||
"ttf" => "font/ttf",
|
||||
"woff" => "font/woff",
|
||||
"woff2" => "font/woff2"
|
||||
];
|
||||
|
||||
function getMimeType($path) {
|
||||
global $mimeDefault, $mimeTypes;
|
||||
|
||||
$i = strrpos($path, ".");
|
||||
if ($i === false) return $mimeDefault;
|
||||
|
||||
$s = strrpos($path, "/");
|
||||
if ($s !== false && $i < $s) return $mimeDefault;
|
||||
|
||||
$suffix = substr($path, $i + 1);
|
||||
if (isset($mimeTypes[$suffix]))
|
||||
return $mimeTypes[$suffix];
|
||||
|
||||
return $mimeDefault;
|
||||
}
|
||||
|
||||
// determine relative request-path
|
||||
$root = dirname($_SERVER['PHP_SELF']);
|
||||
if ($root === "/" || $root === "\\") $root = "";
|
||||
$uriPath = $_SERVER['REQUEST_URI'];
|
||||
$path = substr($uriPath, strlen($root));
|
||||
|
||||
// add /
|
||||
if ($path === "") {
|
||||
header("Location: $uriPath/");
|
||||
exit;
|
||||
}
|
||||
|
||||
// root => index.html
|
||||
if ($path === "/") {
|
||||
header("Content-Type: text/html");
|
||||
echo file_get_contents("index.html");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (startsWith($path, "/maps/")) {
|
||||
|
||||
// determine map-path
|
||||
$pathParts = explode("/", substr($path, strlen("/maps/")), 2);
|
||||
$mapId = $pathParts[0];
|
||||
$mapPath = explode("?", $pathParts[1], 2)[0];
|
||||
|
||||
// get sql-connection
|
||||
$sql = new mysqli($hostname, $username, $password, $database, $port);
|
||||
if ($sql->errno) error(500, "Failed to connect to Database!");
|
||||
|
||||
// provide map-tiles
|
||||
if (startsWith($mapPath, "tiles/")) {
|
||||
|
||||
// parse tile-coordinates
|
||||
preg_match_all("/tiles\/([\d\/]+)\/x(-?[\d\/]+)z(-?[\d\/]+).*/", $mapPath, $matches);
|
||||
$lod = intval($matches[1][0]);
|
||||
$tileX = intval(str_replace("/", "", $matches[2][0]));
|
||||
$tileZ = intval(str_replace("/", "", $matches[3][0]));
|
||||
$compression = $lod === 0 ? $hiresCompression : "none";
|
||||
|
||||
// query for tile
|
||||
$statement = $sql->prepare("
|
||||
SELECT t.`data`
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
INNER JOIN `bluemap_map_tile_compression` c
|
||||
ON t.`compression` = c.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`lod` = ?
|
||||
AND t.`x` = ?
|
||||
AND t.`z` = ?
|
||||
AND c.`compression` = ?
|
||||
");
|
||||
$statement->bind_param("siiis", $mapId, $lod, $tileX, $tileZ, $compression);
|
||||
$statement->execute();
|
||||
if ($statement->errno) error(500, "Database query failed!");
|
||||
|
||||
// return result
|
||||
$result = $statement->get_result();
|
||||
if ($result && $line = $result->fetch_assoc()) {
|
||||
if ($compression !== "none")
|
||||
header("Content-Encoding: $compression");
|
||||
|
||||
if ($lod === 0) {
|
||||
header("Content-Type: application/json");
|
||||
} else {
|
||||
header("Content-Type: image/png");
|
||||
}
|
||||
|
||||
echo $line["data"];
|
||||
exit;
|
||||
}
|
||||
|
||||
// empty json response if nothing found
|
||||
header("Content-Type: application/json");
|
||||
echo "{}";
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
// provide meta-files
|
||||
$statement = $sql->prepare("
|
||||
SELECT t.`value`
|
||||
FROM `bluemap_map_meta` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`key` = ?
|
||||
");
|
||||
$statement->bind_param("ss", $mapId, $mapPath);
|
||||
$statement->execute();
|
||||
if ($statement->errno) error(500, "Database query failed!");
|
||||
|
||||
$result = $statement->get_result();
|
||||
if ($result && $line = $result->fetch_assoc()) {
|
||||
header("Content-Type: ".getMimeType($mapPath));
|
||||
echo $line["value"];
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// no match => 404
|
||||
error(404);
|
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
|
||||
// !!! SET YOUR SQL-CONNECTION SETTINGS HERE: !!!
|
||||
|
||||
$driver = 'mysql'; // 'mysql' (MySQL) or 'pgsql' (PostgreSQL)
|
||||
$hostname = '127.0.0.1';
|
||||
$port = 3306;
|
||||
$username = 'root';
|
||||
$password = '';
|
||||
$database = 'bluemap';
|
||||
|
||||
// !!! END - DONT CHANGE ANYTHING AFTER THIS LINE !!!
|
||||
|
||||
|
||||
|
||||
|
||||
// compression
|
||||
$compressionHeaderMap = [
|
||||
"bluemap:none" => null,
|
||||
"bluemap:gzip" => "gzip",
|
||||
"bluemap:deflate" => "deflate",
|
||||
"bluemap:zstd" => "zstd",
|
||||
"bluemap:lz4" => "lz4"
|
||||
];
|
||||
|
||||
// meta files
|
||||
$metaFileKeys = [
|
||||
"settings.json" => "bluemap:settings",
|
||||
"textures.json" => "bluemap:textures",
|
||||
"live/markers.json" => "bluemap:markers",
|
||||
"live/players.json" => "bluemap:players",
|
||||
];
|
||||
|
||||
// mime-types for meta-files
|
||||
$mimeDefault = "application/octet-stream";
|
||||
$mimeTypes = [
|
||||
"txt" => "text/plain",
|
||||
"css" => "text/css",
|
||||
"csv" => "text/csv",
|
||||
"htm" => "text/html",
|
||||
"html" => "text/html",
|
||||
"js" => "text/javascript",
|
||||
"xml" => "text/xml",
|
||||
|
||||
"png" => "image/png",
|
||||
"jpg" => "image/jpeg",
|
||||
"jpeg" => "image/jpeg",
|
||||
"gif" => "image/gif",
|
||||
"webp" => "image/webp",
|
||||
"tif" => "image/tiff",
|
||||
"tiff" => "image/tiff",
|
||||
"svg" => "image/svg+xml",
|
||||
|
||||
"json" => "application/json",
|
||||
|
||||
"mp3" => "audio/mpeg",
|
||||
"oga" => "audio/ogg",
|
||||
"wav" => "audio/wav",
|
||||
"weba" => "audio/webm",
|
||||
|
||||
"mp4" => "video/mp4",
|
||||
"mpeg" => "video/mpeg",
|
||||
"webm" => "video/webm",
|
||||
|
||||
"ttf" => "font/ttf",
|
||||
"woff" => "font/woff",
|
||||
"woff2" => "font/woff2"
|
||||
];
|
||||
|
||||
// some helper functions
|
||||
function error($code, $message = null) {
|
||||
global $path;
|
||||
|
||||
http_response_code($code);
|
||||
header("Content-Type: text/plain");
|
||||
echo "BlueMap php-script - $code\n";
|
||||
if ($message != null) echo $message."\n";
|
||||
echo "Requested Path: $path";
|
||||
exit;
|
||||
}
|
||||
|
||||
function startsWith($haystack, $needle) {
|
||||
return substr($haystack, 0, strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
function issetOrElse(& $var, $fallback) {
|
||||
return isset($var) ? $var : $fallback;
|
||||
}
|
||||
|
||||
function compressionHeader($compressionKey) {
|
||||
global $compressionHeaderMap;
|
||||
|
||||
$compressionHeader = issetOrElse($compressionHeaderMap[$compressionKey], null);
|
||||
if ($compressionHeader)
|
||||
header("Content-Encoding: ".$compressionHeader);
|
||||
}
|
||||
|
||||
function getMimeType($path) {
|
||||
global $mimeDefault, $mimeTypes;
|
||||
|
||||
$i = strrpos($path, ".");
|
||||
if ($i === false) return $mimeDefault;
|
||||
|
||||
$s = strrpos($path, "/");
|
||||
if ($s !== false && $i < $s) return $mimeDefault;
|
||||
|
||||
$suffix = substr($path, $i + 1);
|
||||
if (isset($mimeTypes[$suffix]))
|
||||
return $mimeTypes[$suffix];
|
||||
|
||||
return $mimeDefault;
|
||||
}
|
||||
|
||||
function send($data) {
|
||||
if (is_resource($data)) {
|
||||
fpassthru($data);
|
||||
} else {
|
||||
echo $data;
|
||||
}
|
||||
}
|
||||
|
||||
// determine relative request-path
|
||||
$root = dirname($_SERVER['PHP_SELF']);
|
||||
if ($root === "/" || $root === "\\") $root = "";
|
||||
$uriPath = $_SERVER['REQUEST_URI'];
|
||||
$path = substr($uriPath, strlen($root));
|
||||
|
||||
// add /
|
||||
if ($path === "") {
|
||||
header("Location: $uriPath/");
|
||||
exit;
|
||||
}
|
||||
|
||||
// root => index.html
|
||||
if ($path === "/") {
|
||||
header("Content-Type: text/html");
|
||||
echo file_get_contents("index.html");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (startsWith($path, "/maps/")) {
|
||||
|
||||
// determine map-path
|
||||
$pathParts = explode("/", substr($path, strlen("/maps/")), 2);
|
||||
$mapId = $pathParts[0];
|
||||
$mapPath = explode("?", $pathParts[1], 2)[0];
|
||||
|
||||
// Initialize PDO
|
||||
try {
|
||||
$sql = new PDO("$driver:host=$hostname;port=$port;dbname=$database", $username, $password);
|
||||
$sql->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e ) { error(500, "Failed to connect to database"); }
|
||||
|
||||
// provide map-tiles
|
||||
if (startsWith($mapPath, "tiles/")) {
|
||||
|
||||
// parse tile-coordinates
|
||||
preg_match_all("/tiles\/([\d\/]+)\/x(-?[\d\/]+)z(-?[\d\/]+).*/", $mapPath, $matches);
|
||||
$lod = intval($matches[1][0]);
|
||||
$storage = $lod === 0 ? "bluemap:hires" : "bluemap:lowres/".$lod;
|
||||
$tileX = intval(str_replace("/", "", $matches[2][0]));
|
||||
$tileZ = intval(str_replace("/", "", $matches[3][0]));
|
||||
|
||||
// query for tile
|
||||
try {
|
||||
$statement = $sql->prepare("
|
||||
SELECT d.data, c.key
|
||||
FROM bluemap_grid_storage_data d
|
||||
INNER JOIN bluemap_map m
|
||||
ON d.map = m.id
|
||||
INNER JOIN bluemap_grid_storage s
|
||||
ON d.storage = s.id
|
||||
INNER JOIN bluemap_compression c
|
||||
ON d.compression = c.id
|
||||
WHERE m.map_id = :map_id
|
||||
AND s.key = :storage
|
||||
AND d.x = :x
|
||||
AND d.z = :z
|
||||
");
|
||||
$statement->bindParam( ':map_id', $mapId, PDO::PARAM_STR );
|
||||
$statement->bindParam( ':storage', $storage, PDO::PARAM_STR );
|
||||
$statement->bindParam( ':x', $tileX, PDO::PARAM_INT );
|
||||
$statement->bindParam( ':z', $tileZ, PDO::PARAM_INT );
|
||||
$statement->setFetchMode(PDO::FETCH_ASSOC);
|
||||
$statement->execute();
|
||||
|
||||
// return result
|
||||
if ($line = $statement->fetch()) {
|
||||
header("Cache-Control: public,max-age=86400");
|
||||
compressionHeader($line["key"]);
|
||||
|
||||
if ($lod === 0) {
|
||||
header("Content-Type: application/octet-stream");
|
||||
} else {
|
||||
header("Content-Type: image/png");
|
||||
}
|
||||
|
||||
send($line["data"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (PDOException $e) { error(500, "Failed to fetch data"); }
|
||||
|
||||
// no content if nothing found
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
|
||||
// provide meta-files
|
||||
$storage = issetOrElse($metaFileKeys[$mapPath], null);
|
||||
if ($storage === null && startsWith($mapPath, "assets/"))
|
||||
$storage = "bluemap:asset/".substr($mapPath, strlen("assets/"));
|
||||
|
||||
if ($storage !== null) {
|
||||
try {
|
||||
$statement = $sql->prepare("
|
||||
SELECT d.data, c.key
|
||||
FROM bluemap_item_storage_data d
|
||||
INNER JOIN bluemap_map m
|
||||
ON d.map = m.id
|
||||
INNER JOIN bluemap_item_storage s
|
||||
ON d.storage = s.id
|
||||
INNER JOIN bluemap_compression c
|
||||
ON d.compression = c.id
|
||||
WHERE m.map_id = :map_id
|
||||
AND s.key = :storage
|
||||
");
|
||||
$statement->bindParam( ':map_id', $mapId, PDO::PARAM_STR );
|
||||
$statement->bindParam( ':storage', $storage, PDO::PARAM_STR );
|
||||
$statement->setFetchMode(PDO::FETCH_ASSOC);
|
||||
$statement->execute();
|
||||
|
||||
if ($line = $statement->fetch()) {
|
||||
header("Cache-Control: public,max-age=86400");
|
||||
header("Content-Type: ".getMimeType($mapPath));
|
||||
compressionHeader($line["key"]);
|
||||
|
||||
send($line["data"]);
|
||||
exit;
|
||||
}
|
||||
} catch (PDOException $e) { error(500, "Failed to fetch data"); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// no match => 404
|
||||
error(404);
|
|
@ -2,13 +2,13 @@
|
|||
<div class="control-bar">
|
||||
<MenuButton :close="appState.menu.isOpen" :back="false" @action="appState.menu.reOpenPage()" :title="$t('menu.tooltip')" />
|
||||
<div class="space thin-hide"></div>
|
||||
<SvgButton v-if="appState.maps.length > 0" class="thin-hide" :title="$t('maps.tooltip')"
|
||||
<SvgButton v-if="appState.maps.length > 1" class="thin-hide" :title="$t('maps.tooltip')"
|
||||
@action="appState.menu.openPage('maps', $t('maps.title'))">
|
||||
<svg viewBox="0 0 30 30">
|
||||
<polygon points="26.708,22.841 19.049,25.186 11.311,20.718 3.292,22.841 7.725,5.96 13.475,4.814 19.314,7.409 25.018,6.037 "/>
|
||||
</svg>
|
||||
</SvgButton>
|
||||
<SvgButton v-if="showMapMenu && (markers.markerSets.length > 0 || markers.markers.length > 0)" class="thin-hide" :title="$t('markers.tooltip')"
|
||||
<SvgButton v-if="showMapMenu && showMarkerMenu" class="thin-hide" :title="$t('markers.tooltip')"
|
||||
@action="appState.menu.openPage('markers', $t('markers.title'), {markerSet: markers})">
|
||||
<svg viewBox="0 0 30 30">
|
||||
<path d="M15,3.563c-4.459,0-8.073,3.615-8.073,8.073c0,6.483,8.196,14.802,8.196,14.802s7.951-8.013,7.951-14.802
|
||||
|
@ -101,12 +101,24 @@
|
|||
},
|
||||
showMapMenu() {
|
||||
return this.mapViewer.mapState === "loading" || this.mapViewer.mapState === "loaded";
|
||||
},
|
||||
showMarkerMenu() {
|
||||
return this.hasMarkers(this.markers)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openPlayerList() {
|
||||
let playerList = this.playerMarkerSet;
|
||||
this.appState.menu.openPage('markers', this.$t("players.title"), {markerSet: playerList});
|
||||
},
|
||||
hasMarkers(markerSet) {
|
||||
if (markerSet.markers.length > 0) return true;
|
||||
for (let set of markerSet.markerSets) {
|
||||
if (set.id !== "bm-players" && set.id !== "bm-popup-set") {
|
||||
if (this.hasMarkers(set)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ export default {
|
|||
animation = animate(t => {
|
||||
let u = EasingFunctions.easeOutQuad(t);
|
||||
this.mapViewer.uniforms.sunlightStrength.value = startValue * (1-u) + targetValue * u;
|
||||
this.$bluemap.mapViewer.redraw();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
<SwitchHandle :on="markerSet.visible" v-if="markerSet.toggleable"/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div v-if="markerSet.markers.length > 0">
|
||||
{{ markerSet.markers.length }}
|
||||
{{ $t('markers.marker', markerSet.markers.length) }}
|
||||
<div v-if="filteredMarkerCount > 0">
|
||||
{{ filteredMarkerCount }}
|
||||
{{ $t('markers.marker', filteredMarkerCount) }}
|
||||
</div>
|
||||
<div v-if="filteredMarkerSetCount > 0">
|
||||
{{ filteredMarkerSetCount }}
|
||||
|
@ -46,6 +46,13 @@ export default {
|
|||
}
|
||||
return count;
|
||||
},
|
||||
filteredMarkerCount() {
|
||||
let count = 0;
|
||||
for (let marker of this.markerSet.markers) {
|
||||
if (marker.listed) count++;
|
||||
}
|
||||
return count;
|
||||
},
|
||||
label() {
|
||||
if (this.markerSet.id === "bm-players") return this.$t("players.title");
|
||||
return this.markerSet.label;
|
||||
|
|
|
@ -82,9 +82,13 @@ export default {
|
|||
openMore(markerSet) {
|
||||
this.menu.openPage(
|
||||
this.menu.currentPage().id,
|
||||
this.menu.currentPage().title + " > " + markerSet.label,
|
||||
this.menu.currentPage().title + " > " + this.labelOf(markerSet),
|
||||
{markerSet: markerSet}
|
||||
)
|
||||
},
|
||||
labelOf(markerSet) {
|
||||
if (markerSet.id === "bm-players") return this.$t("players.title");
|
||||
return markerSet.label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,20 +3,20 @@
|
|||
<Group :title="$t('controls.title')">
|
||||
<SimpleButton :active="appState.controls.state === 'perspective'" @action="$bluemap.setPerspectiveView(500, appState.controls.state === 'free' ? 100 : 0)">{{$t('controls.perspective.button')}}</SimpleButton>
|
||||
<SimpleButton :active="appState.controls.state === 'flat'" @action="$bluemap.setFlatView(500, appState.controls.state === 'free' ? 100 : 0)">{{$t('controls.flatView.button')}}</SimpleButton>
|
||||
<SimpleButton :active="appState.controls.state === 'free'" @action="$bluemap.setFreeFlight(500)">{{$t('controls.freeFlight.button')}}</SimpleButton>
|
||||
<SimpleButton v-if="appState.controls.enableFreeFlight" :active="appState.controls.state === 'free'" @action="$bluemap.setFreeFlight(500)">{{$t('controls.freeFlight.button')}}</SimpleButton>
|
||||
</Group>
|
||||
|
||||
<Group :title="$t('lighting.title')">
|
||||
<Slider :value="mapViewer.uniforms.sunlightStrength.value" :min="0" :max="1" :step="0.01"
|
||||
@update="mapViewer.uniforms.sunlightStrength.value = $event">{{$t('lighting.sunlight')}}</Slider>
|
||||
@update="mapViewer.uniforms.sunlightStrength.value = $event; $bluemap.mapViewer.redraw()">{{$t('lighting.sunlight')}}</Slider>
|
||||
<Slider :value="mapViewer.uniforms.ambientLight.value" :min="0" :max="1" :step="0.01"
|
||||
@update="mapViewer.uniforms.ambientLight.value = $event">{{$t('lighting.ambientLight')}}</Slider>
|
||||
@update="mapViewer.uniforms.ambientLight.value = $event; $bluemap.mapViewer.redraw()">{{$t('lighting.ambientLight')}}</Slider>
|
||||
</Group>
|
||||
|
||||
<Group :title="$t('resolution.title')">
|
||||
<SimpleButton v-for="stage of qualityStages" :key="stage.name"
|
||||
:active="mapViewer.superSampling === stage.value"
|
||||
@action="$bluemap.mapViewer.superSampling = stage.value; $bluemap.saveUserSettings();"
|
||||
@action="$bluemap.mapViewer.superSampling = stage.value; $bluemap.saveUserSettings(); $bluemap.mapViewer.redraw()"
|
||||
>{{stage.name}}</SimpleButton>
|
||||
</Group>
|
||||
|
||||
|
@ -56,6 +56,8 @@
|
|||
>{{lang.name}}</SimpleButton>
|
||||
</Group>
|
||||
|
||||
<SwitchButton :on="mapViewer.uniforms.chunkBorders.value" @action="switchChunkBorders(); $bluemap.saveUserSettings();">{{ $t("chunkBorders.button") }}</SwitchButton>
|
||||
|
||||
<SwitchButton :on="appState.debug" @action="switchDebug(); $bluemap.saveUserSettings();">{{ $t("debug.button") }}</SwitchButton>
|
||||
|
||||
<SimpleButton @action="$bluemap.resetSettings()">{{ $t("resetAllSettings.button") }}</SimpleButton>
|
||||
|
@ -105,6 +107,9 @@ name: "SettingsMenu",
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
switchChunkBorders() {
|
||||
this.$bluemap.setChunkBorders(!this.mapViewer.uniforms.chunkBorders.value);
|
||||
},
|
||||
switchDebug() {
|
||||
this.$bluemap.setDebug(!this.appState.debug);
|
||||
},
|
||||
|
@ -121,4 +126,4 @@ name: "SettingsMenu",
|
|||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -146,6 +146,7 @@ export class BlueMapApp {
|
|||
let styleElement = document.createElement("link");
|
||||
styleElement.rel = "stylesheet";
|
||||
styleElement.href = styleUrl;
|
||||
alert(this.events, "Loading style: " + styleUrl, "fine");
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
|
||||
|
@ -153,6 +154,9 @@ export class BlueMapApp {
|
|||
await this.mapViewer.switchMap(null);
|
||||
oldMaps.forEach(map => map.dispose());
|
||||
|
||||
// load user settings
|
||||
await this.loadUserSettings();
|
||||
|
||||
// load maps
|
||||
this.maps = await this.loadMaps();
|
||||
for (let map of this.maps) {
|
||||
|
@ -179,9 +183,6 @@ export class BlueMapApp {
|
|||
if(this.updateLoop) clearTimeout(this.updateLoop);
|
||||
this.updateLoop = setTimeout(this.update, 1000);
|
||||
|
||||
// load user settings
|
||||
await this.loadUserSettings();
|
||||
|
||||
// save user settings
|
||||
this.saveUserSettings();
|
||||
|
||||
|
@ -189,6 +190,7 @@ export class BlueMapApp {
|
|||
if (this.settings.scripts) for (let scriptUrl of this.settings.scripts) {
|
||||
let scriptElement = document.createElement("script");
|
||||
scriptElement.src = scriptUrl;
|
||||
alert(this.events, "Loading script: " + scriptUrl, "fine");
|
||||
document.body.appendChild(scriptElement);
|
||||
}
|
||||
}
|
||||
|
@ -297,15 +299,17 @@ export class BlueMapApp {
|
|||
|
||||
// create maps
|
||||
if (settings.maps !== undefined){
|
||||
for (let mapId of settings.maps) {
|
||||
let loadingPromises = settings.maps.map(mapId => {
|
||||
let map = new BlueMapMap(mapId, this.dataUrl + mapId + "/", this.loadBlocker, this.mapViewer.events);
|
||||
maps.push(map);
|
||||
|
||||
await map.loadSettings()
|
||||
return map.loadSettings(this.mapViewer.tileCacheHash)
|
||||
.catch(error => {
|
||||
alert(this.events, `Failed to load settings for map '${map.data.id}':` + error, "warning");
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(loadingPromises);
|
||||
}
|
||||
|
||||
// sort maps
|
||||
|
@ -521,6 +525,10 @@ export class BlueMapApp {
|
|||
this.appState.controls.state = "free";
|
||||
}
|
||||
|
||||
setChunkBorders(chunkBorders) {
|
||||
this.mapViewer.data.uniforms.chunkBorders.value = chunkBorders;
|
||||
}
|
||||
|
||||
setDebug(debug) {
|
||||
this.appState.debug = debug;
|
||||
|
||||
|
@ -604,6 +612,7 @@ export class BlueMapApp {
|
|||
this.setTheme(this.loadUserSetting("theme", this.appState.theme));
|
||||
this.setScreenshotClipboard(this.loadUserSetting("screenshotClipboard", this.appState.screenshot.clipboard));
|
||||
await setLanguage(this.loadUserSetting("lang", i18n.locale.value));
|
||||
this.setChunkBorders(this.loadUserSetting("chunkBorders", this.mapViewer.data.uniforms.chunkBorders.value))
|
||||
this.setDebug(this.loadUserSetting("debug", this.appState.debug));
|
||||
|
||||
alert(this.events, "Settings loaded!", "info");
|
||||
|
@ -625,6 +634,7 @@ export class BlueMapApp {
|
|||
this.saveUserSetting("theme", this.appState.theme);
|
||||
this.saveUserSetting("screenshotClipboard", this.appState.screenshot.clipboard);
|
||||
this.saveUserSetting("lang", i18n.locale.value);
|
||||
this.saveUserSetting("chunkBorders", this.mapViewer.data.uniforms.chunkBorders.value);
|
||||
this.saveUserSetting("debug", this.appState.debug);
|
||||
|
||||
alert(this.events, "Settings saved!", "info");
|
||||
|
@ -708,6 +718,12 @@ export class BlueMapApp {
|
|||
}
|
||||
}
|
||||
|
||||
switch (values[9]) {
|
||||
case "flat" : this.setFlatView(0); break;
|
||||
case "free" : this.setFreeFlight(0, controls.position.y); break;
|
||||
default : this.setPerspectiveView(0); break;
|
||||
}
|
||||
|
||||
controls.position.x = parseFloat(values[1]);
|
||||
controls.position.y = parseFloat(values[2]);
|
||||
controls.position.z = parseFloat(values[3]);
|
||||
|
@ -717,11 +733,8 @@ export class BlueMapApp {
|
|||
controls.tilt = parseFloat(values[7]);
|
||||
controls.ortho = parseFloat(values[8]);
|
||||
|
||||
switch (values[9]) {
|
||||
case "flat" : this.setFlatView(0); break;
|
||||
case "free" : this.setFreeFlight(0, controls.position.y); break;
|
||||
default : this.setPerspectiveView(0); break;
|
||||
}
|
||||
this.updatePageAddress();
|
||||
this.mapViewer.updateLoadedMapArea();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,8 @@ export class MapViewer {
|
|||
sunlightStrength: { value: 1 },
|
||||
ambientLight: { value: 0 },
|
||||
skyColor: { value: new Color(0.5, 0.5, 1) },
|
||||
voidColor: { value: new Color(0, 0, 0) },
|
||||
chunkBorders: { value: false },
|
||||
hiresTileMap: {
|
||||
value: {
|
||||
map: null,
|
||||
|
@ -112,6 +114,9 @@ export class MapViewer {
|
|||
this.markers = new MarkerSet("bm-root");
|
||||
|
||||
this.lastFrame = 0;
|
||||
this.lastRedrawChange = 0;
|
||||
events.addEventListener("bluemapCameraMoved", this.redraw)
|
||||
events.addEventListener("bluemapTileLoaded", this.redraw)
|
||||
|
||||
// initialize
|
||||
this.initializeRootElement();
|
||||
|
@ -159,6 +164,8 @@ export class MapViewer {
|
|||
|
||||
this.camera.aspect = this.rootElement.clientWidth / this.rootElement.clientHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
this.redraw();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -173,8 +180,8 @@ export class MapViewer {
|
|||
handleMapInteraction(screenPosition, data = {}) {
|
||||
let rootOffset = elementOffset(this.rootElement);
|
||||
let normalizedScreenPos = new Vector2(
|
||||
((screenPosition.x - rootOffset.top) / this.rootElement.clientWidth) * 2 - 1,
|
||||
-((screenPosition.y - rootOffset.left) / this.rootElement.clientHeight) * 2 + 1
|
||||
((screenPosition.x - rootOffset.left) / this.rootElement.clientWidth) * 2 - 1,
|
||||
-((screenPosition.y - rootOffset.top) / this.rootElement.clientHeight) * 2 + 1
|
||||
);
|
||||
|
||||
if (this.map && this.map.isLoaded){
|
||||
|
@ -260,6 +267,13 @@ export class MapViewer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to wake up the render-loop and render on high-fps for a while
|
||||
*/
|
||||
redraw = () => {
|
||||
this.lastRedrawChange = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* The render-loop to update and possibly render a new frame.
|
||||
|
@ -271,7 +285,6 @@ export class MapViewer {
|
|||
// calculate delta time
|
||||
if (this.lastFrame <= 0) this.lastFrame = now;
|
||||
let delta = now - this.lastFrame;
|
||||
this.lastFrame = now;
|
||||
|
||||
// update stats
|
||||
this.stats.begin();
|
||||
|
@ -282,7 +295,10 @@ export class MapViewer {
|
|||
}
|
||||
|
||||
// render
|
||||
this.render(delta);
|
||||
if (delta >= 50 || Date.now() - this.lastRedrawChange < 1000) {
|
||||
this.lastFrame = now;
|
||||
this.render(delta);
|
||||
}
|
||||
|
||||
// update stats
|
||||
this.stats.update();
|
||||
|
@ -310,6 +326,8 @@ export class MapViewer {
|
|||
|
||||
if (this.map && this.map.isLoaded) {
|
||||
|
||||
this.map.animations.forEach(animation => animation.step(delta))
|
||||
|
||||
// shift whole scene including camera towards 0,0 to tackle shader-precision issues
|
||||
const s = 10000;
|
||||
const sX = Math.round(this.camera.position.x / s) * s;
|
||||
|
@ -393,6 +411,7 @@ export class MapViewer {
|
|||
|
||||
this.data.uniforms.distance.value = this.controlsManager.distance;
|
||||
this.data.uniforms.skyColor.value = map.data.skyColor;
|
||||
this.data.uniforms.voidColor.value = map.data.voidColor;
|
||||
this.data.uniforms.ambientLight.value = map.data.ambientLight;
|
||||
this.data.uniforms.hiresTileMap.value.map = map.hiresTileManager.tileMap.texture;
|
||||
this.data.uniforms.hiresTileMap.value.scale.set(map.data.hires.tileSize.x, map.data.hires.tileSize.z);
|
||||
|
|
|
@ -43,6 +43,7 @@ export class PopupMarker extends Marker {
|
|||
|
||||
this.elementObject = new CSS2DObject(htmlToElement(`<div id="bm-marker-${this.data.id}" class="bm-marker-${this.data.type}">Test</div>`));
|
||||
this.elementObject.position.set(0.5, 1, 0.5);
|
||||
this.elementObject.disableDepthTest = true;
|
||||
this.addEventListener( 'removed', () => {
|
||||
if (this.element.parentNode) this.element.parentNode.removeChild(this.element);
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue