From 1c94ced87556e4cd14032b58ce2abcad04ca6e84 Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 6 Feb 2020 11:37:13 +0100 Subject: [PATCH 1/9] [wip] Try to load i18n file with utf-8 encoding --- build.gradle | 4 +- .../chunkmaster/lib/LanguageManager.kt | 22 ++++++-- .../resources/i18n/DEFAULT.i18n.properties | 44 ++++++++-------- src/main/resources/i18n/de.i18n.properties | 52 +++++++++---------- src/main/resources/i18n/en.i18n.properties | 44 ++++++++-------- src/main/resources/plugin.yml | 2 +- 6 files changed, 92 insertions(+), 76 deletions(-) diff --git a/build.gradle b/build.gradle index 247b88c..d2538ec 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ idea { } group "net.trivernis" -version "0.14-beta" +version "0.15-beta" sourceCompatibility = 1.8 @@ -53,6 +53,8 @@ dependencies { compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' compile "io.papermc:paperlib:1.0.2" compile "org.bstats:bstats-bukkit:1.5" + compile "org.apache.commons:commons-lang3:3.9" + compile "org.apache.commons:commons-io:1.3.2" } apply plugin: "com.github.johnrengelman.shadow" diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt index da48546..de4ee97 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt @@ -1,9 +1,13 @@ package net.trivernis.chunkmaster.lib import net.trivernis.chunkmaster.Chunkmaster -import java.io.BufferedReader import java.io.File import java.lang.Exception import java.util.Properties +import org.apache.commons.io.IOUtils +import org.apache.commons.lang.StringEscapeUtils +import java.io.InputStream +import java.io.StringReader +import java.io.StringWriter class LanguageManager(private val plugin: Chunkmaster) { private val langProps = Properties() @@ -22,7 +26,7 @@ class LanguageManager(private val plugin: Chunkmaster) { val loader = Thread.currentThread().contextClassLoader val defaultStream = this.javaClass.getResourceAsStream("/i18n/DEFAULT.i18n.properties") if (defaultStream != null) { - langProps.load(defaultStream) + langProps.load(getReaderForProperties(defaultStream)) defaultStream.close() } else { plugin.logger.severe("Couldn't load default language properties.") @@ -31,7 +35,7 @@ class LanguageManager(private val plugin: Chunkmaster) { try { val inputStream = loader.getResourceAsStream(langFile) if (inputStream != null) { - langProps.load(inputStream) + langProps.load(getReaderForProperties(inputStream)) langFileLoaded = true inputStream.close() } @@ -42,7 +46,7 @@ class LanguageManager(private val plugin: Chunkmaster) { } else { val inputStream = this.javaClass.getResourceAsStream("/i18n/$language.i18n.properties") if (inputStream != null) { - langProps.load(inputStream) + langProps.load(getReaderForProperties(inputStream)) langFileLoaded = true inputStream.close() } else { @@ -58,4 +62,14 @@ class LanguageManager(private val plugin: Chunkmaster) { val localizedString = langProps.getProperty(key) return String.format(localizedString, *replacements) } + + /** + * Reads a properties file as utf-8 and returns a string reader for the contents + */ + private fun getReaderForProperties(stream: InputStream): StringReader { + val writer = StringWriter(); + IOUtils.copy(stream, writer, "UTF-8") + val escapedStringContent = StringEscapeUtils.escapeJava(writer.toString()) + return StringReader(escapedStringContent) + } } \ No newline at end of file diff --git a/src/main/resources/i18n/DEFAULT.i18n.properties b/src/main/resources/i18n/DEFAULT.i18n.properties index e0771de..a692811 100644 --- a/src/main/resources/i18n/DEFAULT.i18n.properties +++ b/src/main/resources/i18n/DEFAULT.i18n.properties @@ -1,48 +1,48 @@ RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'... TASK_FINISHED = Task #%d finished after %d chunks. TASK_CANCELED = Canceled task #%s. -TASK_LOAD_FAILED = \u00A7cFailed to load task #%d. +TASK_LOAD_FAILED = §cFailed to load task #%d. TASK_LOAD_SUCCESS = %d saved tasks loaded. -TASK_NOT_FOUND = \u00A7cTask %s not found! +TASK_NOT_FOUND = §cTask %s not found! CREATE_DELAYED_LOAD = Creating task to load chunk generation Tasks later... TASK_PERIODIC_REPORT = Task #%d running for '%s'. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d -TASK_SAVE_FAILED = \u00A7cException when saving tasks: %s +TASK_SAVE_FAILED = §cException when saving tasks: %s -WORLD_NAME_REQUIRED = \u00A7cYou need to provide a world name! -WORLD_NOT_FOUND = \u00A7cWorld \u00A72%s \u00A7cnot found! -TASK_ALREADY_EXISTS = \u00A7cA task for '%s' already exists! -TASK_CREATION_SUCCESS = \u00A79Generation Task for world \u00A72%s \u00A79 until \u00A72%s \u00A79successfully created! -TASK_ID_REQUIRED = \u00A7cYou need to provide a task id! +WORLD_NAME_REQUIRED = §cYou need to provide a world name! +WORLD_NOT_FOUND = §cWorld §2%s §cnot found! +TASK_ALREADY_EXISTS = §cA task for '%s' already exists! +TASK_CREATION_SUCCESS = §9Generation Task for world §2%s §9 until §2%s §9successfully created! +TASK_ID_REQUIRED = §cYou need to provide a task id! PAUSED_TASKS_HEADER = Currently Paused Generation Tasks -TASKS_ENTRY = - \u00A79#%d\u00A7r - \u00A72%s\u00A7r - \u00A72%d chunks %s\u00A7r +TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%d chunks %s§r RUNNING_TASKS_HEADER = Currently Running Generation Tasks NO_GENERATION_TASKS = There are no generation tasks. -PAUSE_SUCCESS = \u00A79Paused all generation tasks. -ALREADY_PAUSED = \u00A7cThe generation process is already paused! +PAUSE_SUCCESS = §9Paused all generation tasks. +ALREADY_PAUSED = §cThe generation process is already paused! -RESUME_SUCCESS = \u00A79Resumed all generation Tasks. -NOT_PAUSED = \u00A7cThe generation process is not paused! +RESUME_SUCCESS = §9Resumed all generation Tasks. +NOT_PAUSED = §cThe generation process is not paused! CONFIG_RELOADING = Reloading the config file... -CONFIG_RELOADED = \u00A72The config file has been reloaded! +CONFIG_RELOADED = §2The config file has been reloaded! -TELEPORTED = \u00A79You have been teleported to chunk \u00A72%s, %s -TP_ONLY_PLAYER = \u00A7cThis command can only be executed by a player! +TELEPORTED = §9You have been teleported to chunk §2%s, %s +TP_ONLY_PLAYER = §cThis command can only be executed by a player! -NO_PERMISSION = \u00A7cYou do not have the permission for this command! -SUBCOMMAND_NOT_FOUND = \u00A7cSubcommand \u00A72%s \u00A7cnot found! +NO_PERMISSION = §cYou do not have the permission for this command! +SUBCOMMAND_NOT_FOUND = §cSubcommand §2%s §cnot found! STOPPING_ALL_TASKS = Stopping all generation tasks... DB_INIT = Initializing database... DB_INIT_FINISHED = Database fully initialized. DB_INIT_EROR = Failed to init database: %s. -DATABASE_CONNECTION_ERROR = \u00A7cCould not get the database connection! -SQL_ERROR = \u00A7cAn eror occured on sql %s! -NO_DATABASE_CONNECTION = \u00A7cCould not execute sql: No database connection. +DATABASE_CONNECTION_ERROR = §cCould not get the database connection! +SQL_ERROR = §cAn eror occured on sql %s! +NO_DATABASE_CONNECTION = §cCould not execute sql: No database connection. CREATE_TABLE_DEFINITION = Created table %s with definition %s. -TABLE_CREATE_ERROR = \u00A7cError when creation table %s. +TABLE_CREATE_ERROR = §cError when creation table %s. UPDATE_TABLE_DEFINITION = Updated table %s with sql %s. UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. \ No newline at end of file diff --git a/src/main/resources/i18n/de.i18n.properties b/src/main/resources/i18n/de.i18n.properties index e1ee078..307800b 100644 --- a/src/main/resources/i18n/de.i18n.properties +++ b/src/main/resources/i18n/de.i18n.properties @@ -1,48 +1,48 @@ -RESUME_FOR_WORLD = Setze das Chunk-Generieren f\u00fcr Welt '%s' fort... +RESUME_FOR_WORLD = Setze das Chunk-Generieren für Welt '%s' fort... TASK_FINISHED = Aufgabe #%d wurde nach %d chunks beendet. TASK_CANCELED = Aufgabe #%s wurde abgebrochen. -TASK_LOAD_FAILED = \u00A7cAufgabe #%d konnte nicht geladen werden. +TASK_LOAD_FAILED = §cAufgabe #%d konnte nicht geladen werden. TASK_LOAD_SUCCESS = %d gespeicherte Aufgaben wurden geladen. -TASK_NOT_FOUND = \u00A7cAufgabe %s konnte nicht gefunden werden! -CREATE_DELAYED_LOAD = Erstelle einen Bukkit-Task zum verz\u00f6gerten Laden von Aufgaben... -TASK_PERIODIC_REPORT = Aufgabe #%d f\u00fcr Welt '%s'. Fortschritt: %d chunks %s %s, Geschwindigkeit: %.1f ch/s, Letzer Chunk: %d, %d -TASK_SAVE_FAILED = \u00A7cFehler beim Speichern der Aufgaben: %s +TASK_NOT_FOUND = §cAufgabe %s konnte nicht gefunden werden! +CREATE_DELAYED_LOAD = Erstelle einen Bukkit-Task zum verzögerten Laden von Aufgaben... +TASK_PERIODIC_REPORT = Aufgabe #%d für Welt '%s'. Fortschritt: %d chunks %s %s, Geschwindigkeit: %.1f ch/s, Letzer Chunk: %d, %d +TASK_SAVE_FAILED = §cFehler beim Speichern der Aufgaben: %s -WORLD_NAME_REQUIRED = \u00A7cDu musst einen Weltennamen angeben! -WORLD_NOT_FOUND = \u00A7c Die Welt \u00A72%s \u00A7cwurde nicht gefunden! -TASK_ALREADY_EXISTS = \u00A7cEs existiert bereits eine Aufgabe f\u00fcr \u00A72%s\u00A7c! -TASK_CREATION_SUCCESS = \u00A79Generierungs-Aufgabe \u00A72%s \u00A79 bis \u00A72%s \u00A79wurde erfolgreich erstellt! -TASK_ID_REQUIRED = \u00A7cDu musst eine Aufgaben-Id angeben! +WORLD_NAME_REQUIRED = §cDu musst einen Weltennamen angeben! +WORLD_NOT_FOUND = §c Die Welt §2%s §cwurde nicht gefunden! +TASK_ALREADY_EXISTS = §cEs existiert bereits eine Aufgabe für §2%s§c! +TASK_CREATION_SUCCESS = §9Generierungs-Aufgabe §2%s §9 bis §2%s §9wurde erfolgreich erstellt! +TASK_ID_REQUIRED = §cDu musst eine Aufgaben-Id angeben! -PAUSED_TASKS_HEADER = \u00A7lPausierte Generierungsaufgaben\u00A7r +PAUSED_TASKS_HEADER = §lPausierte Generierungsaufgaben§r -RUNNING_TASKS_HEADER = \u00A7lLaufende Generierungsaufgaben\u00A7r +RUNNING_TASKS_HEADER = §lLaufende Generierungsaufgaben§r NO_GENERATION_TASKS = Es gibt keine Aufgaben. -PAUSE_SUCCESS = \u00A79Alle Aufgaben wurden pausiert. -ALREADY_PAUSED = \u00A7cDas Generieren ist bereits pausiert. +PAUSE_SUCCESS = §9Alle Aufgaben wurden pausiert. +ALREADY_PAUSED = §cDas Generieren ist bereits pausiert. -RESUME_SUCCESS = \u00A79Alle Aufgaben wurden fortgesetzt. -NOT_PAUSED = \u00A7cEs gibt keine pausierten Aufgaben! +RESUME_SUCCESS = §9Alle Aufgaben wurden fortgesetzt. +NOT_PAUSED = §cEs gibt keine pausierten Aufgaben! CONFIG_RELOADING = Die Konfigurationsdatei wird neu eingelesen... -CONFIG_RELOADED = \u00A72Die Konfigurationsdatei wurde neu geladen! +CONFIG_RELOADED = §2Die Konfigurationsdatei wurde neu geladen! -TELEPORTED = \u00A79Du wurdest zum Chunk \u00A72%s, %s \u00A79teleportiert -TP_ONLY_PLAYER = \u00A7cDieser Befehl kann nur von einem Spieler ausgef\u00fchrt werden. +TELEPORTED = §9Du wurdest zum Chunk §2%s, %s §9teleportiert +TP_ONLY_PLAYER = §cDieser Befehl kann nur von einem Spieler ausgeführt werden. -NO_PERMISSION = \u00A7cDu hast nicht die Rechte für diesen Befehl! -SUBCOMMAND_NOT_FOUND = \u00A7cUnteraktion \u00A72%s \u00A7cwurde nicht gefunden! +NO_PERMISSION = §cDu hast nicht die Rechte für diesen Befehl! +SUBCOMMAND_NOT_FOUND = §cUnteraktion §2%s §cwurde nicht gefunden! STOPPING_ALL_TASKS = Stoppt alle Aufgaben... DB_INIT = Initialisiere Datenbank... DB_INIT_FINISHED = Die Datenbank wurde initialisiert. DB_INIT_EROR = Fehler beim Initalisieren der Datenbank: %s. -DATABASE_CONNECTION_ERROR = \u00A7cDie Datenbankverbindung konnte nicht erzeugt werden. -SQL_ERROR = \u00A7cEin Fehler trat mit sql %s auf! -NO_DATABASE_CONNECTION = \u00A7cSql konnte nicht ausgef\u00fchrt werden: Keine Datenbankverbindung. +DATABASE_CONNECTION_ERROR = §cDie Datenbankverbindung konnte nicht erzeugt werden. +SQL_ERROR = §cEin Fehler trat mit sql %s auf! +NO_DATABASE_CONNECTION = §cSql konnte nicht ausgeführt werden: Keine Datenbankverbindung. CREATE_TABLE_DEFINITION = Tabelle %s mit Definition %s wurde erstellt. -TABLE_CREATE_ERROR = \u00A7cFehler beim erstellen der Tabelle %s. +TABLE_CREATE_ERROR = §cFehler beim erstellen der Tabelle %s. UPDATE_TABLE_DEFINITION = Tabelle %s wurde mit sql %s geupdated. UPDATE_TABLE_FAILED = Fehler beim Updaten der Tabelle %s mit sql %s. \ No newline at end of file diff --git a/src/main/resources/i18n/en.i18n.properties b/src/main/resources/i18n/en.i18n.properties index e0771de..a692811 100644 --- a/src/main/resources/i18n/en.i18n.properties +++ b/src/main/resources/i18n/en.i18n.properties @@ -1,48 +1,48 @@ RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'... TASK_FINISHED = Task #%d finished after %d chunks. TASK_CANCELED = Canceled task #%s. -TASK_LOAD_FAILED = \u00A7cFailed to load task #%d. +TASK_LOAD_FAILED = §cFailed to load task #%d. TASK_LOAD_SUCCESS = %d saved tasks loaded. -TASK_NOT_FOUND = \u00A7cTask %s not found! +TASK_NOT_FOUND = §cTask %s not found! CREATE_DELAYED_LOAD = Creating task to load chunk generation Tasks later... TASK_PERIODIC_REPORT = Task #%d running for '%s'. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d -TASK_SAVE_FAILED = \u00A7cException when saving tasks: %s +TASK_SAVE_FAILED = §cException when saving tasks: %s -WORLD_NAME_REQUIRED = \u00A7cYou need to provide a world name! -WORLD_NOT_FOUND = \u00A7cWorld \u00A72%s \u00A7cnot found! -TASK_ALREADY_EXISTS = \u00A7cA task for '%s' already exists! -TASK_CREATION_SUCCESS = \u00A79Generation Task for world \u00A72%s \u00A79 until \u00A72%s \u00A79successfully created! -TASK_ID_REQUIRED = \u00A7cYou need to provide a task id! +WORLD_NAME_REQUIRED = §cYou need to provide a world name! +WORLD_NOT_FOUND = §cWorld §2%s §cnot found! +TASK_ALREADY_EXISTS = §cA task for '%s' already exists! +TASK_CREATION_SUCCESS = §9Generation Task for world §2%s §9 until §2%s §9successfully created! +TASK_ID_REQUIRED = §cYou need to provide a task id! PAUSED_TASKS_HEADER = Currently Paused Generation Tasks -TASKS_ENTRY = - \u00A79#%d\u00A7r - \u00A72%s\u00A7r - \u00A72%d chunks %s\u00A7r +TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%d chunks %s§r RUNNING_TASKS_HEADER = Currently Running Generation Tasks NO_GENERATION_TASKS = There are no generation tasks. -PAUSE_SUCCESS = \u00A79Paused all generation tasks. -ALREADY_PAUSED = \u00A7cThe generation process is already paused! +PAUSE_SUCCESS = §9Paused all generation tasks. +ALREADY_PAUSED = §cThe generation process is already paused! -RESUME_SUCCESS = \u00A79Resumed all generation Tasks. -NOT_PAUSED = \u00A7cThe generation process is not paused! +RESUME_SUCCESS = §9Resumed all generation Tasks. +NOT_PAUSED = §cThe generation process is not paused! CONFIG_RELOADING = Reloading the config file... -CONFIG_RELOADED = \u00A72The config file has been reloaded! +CONFIG_RELOADED = §2The config file has been reloaded! -TELEPORTED = \u00A79You have been teleported to chunk \u00A72%s, %s -TP_ONLY_PLAYER = \u00A7cThis command can only be executed by a player! +TELEPORTED = §9You have been teleported to chunk §2%s, %s +TP_ONLY_PLAYER = §cThis command can only be executed by a player! -NO_PERMISSION = \u00A7cYou do not have the permission for this command! -SUBCOMMAND_NOT_FOUND = \u00A7cSubcommand \u00A72%s \u00A7cnot found! +NO_PERMISSION = §cYou do not have the permission for this command! +SUBCOMMAND_NOT_FOUND = §cSubcommand §2%s §cnot found! STOPPING_ALL_TASKS = Stopping all generation tasks... DB_INIT = Initializing database... DB_INIT_FINISHED = Database fully initialized. DB_INIT_EROR = Failed to init database: %s. -DATABASE_CONNECTION_ERROR = \u00A7cCould not get the database connection! -SQL_ERROR = \u00A7cAn eror occured on sql %s! -NO_DATABASE_CONNECTION = \u00A7cCould not execute sql: No database connection. +DATABASE_CONNECTION_ERROR = §cCould not get the database connection! +SQL_ERROR = §cAn eror occured on sql %s! +NO_DATABASE_CONNECTION = §cCould not execute sql: No database connection. CREATE_TABLE_DEFINITION = Created table %s with definition %s. -TABLE_CREATE_ERROR = \u00A7cError when creation table %s. +TABLE_CREATE_ERROR = §cError when creation table %s. UPDATE_TABLE_DEFINITION = Updated table %s with sql %s. UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index dee18cb..19804bf 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ main: net.trivernis.chunkmaster.Chunkmaster name: Chunkmaster -version: '0.14-beta' +version: '0.15-beta' description: Chunk commands plugin. author: Trivernis website: trivernis.net From e5f812e3bc5542d86193e8170279689fe2626d72 Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 6 Feb 2020 20:15:32 +0100 Subject: [PATCH 2/9] Fix unicode loading of i18n files --- .../net/trivernis/chunkmaster/lib/LanguageManager.kt | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt index de4ee97..6fbf156 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt @@ -1,13 +1,10 @@ package net.trivernis.chunkmaster.lib import net.trivernis.chunkmaster.Chunkmaster -import java.io.File import java.lang.Exception import java.util.Properties import org.apache.commons.io.IOUtils import org.apache.commons.lang.StringEscapeUtils -import java.io.InputStream -import java.io.StringReader -import java.io.StringWriter +import java.io.* class LanguageManager(private val plugin: Chunkmaster) { private val langProps = Properties() @@ -66,10 +63,7 @@ class LanguageManager(private val plugin: Chunkmaster) { /** * Reads a properties file as utf-8 and returns a string reader for the contents */ - private fun getReaderForProperties(stream: InputStream): StringReader { - val writer = StringWriter(); - IOUtils.copy(stream, writer, "UTF-8") - val escapedStringContent = StringEscapeUtils.escapeJava(writer.toString()) - return StringReader(escapedStringContent) + private fun getReaderForProperties(stream: InputStream): Reader { + return BufferedReader(InputStreamReader(stream, "UTF-8")) } } \ No newline at end of file From 109699f6396d429919deaf6d1f99189900b45957 Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 6 Feb 2020 20:18:48 +0100 Subject: [PATCH 3/9] Remove unused dependencies --- build.gradle | 2 -- .../kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/build.gradle b/build.gradle index d2538ec..e9148c8 100644 --- a/build.gradle +++ b/build.gradle @@ -53,8 +53,6 @@ dependencies { compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' compile "io.papermc:paperlib:1.0.2" compile "org.bstats:bstats-bukkit:1.5" - compile "org.apache.commons:commons-lang3:3.9" - compile "org.apache.commons:commons-io:1.3.2" } apply plugin: "com.github.johnrengelman.shadow" diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt index 6fbf156..467cdfa 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/LanguageManager.kt @@ -2,8 +2,6 @@ package net.trivernis.chunkmaster.lib import net.trivernis.chunkmaster.Chunkmaster import java.lang.Exception import java.util.Properties -import org.apache.commons.io.IOUtils -import org.apache.commons.lang.StringEscapeUtils import java.io.* class LanguageManager(private val plugin: Chunkmaster) { From 6cb39ce843b10ebd2db6c8a1ded962b6e74f5eff Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 7 Feb 2020 21:05:33 +0100 Subject: [PATCH 4/9] Remove logging of eta in seconds --- .../trivernis/chunkmaster/lib/generation/GenerationManager.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index b9e0bfb..b005834 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -221,7 +221,6 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server )}%)" else "" val eta = if (genTask.stopAfter > 0 && speed > 0) { val etaSeconds = (genTask.stopAfter - genTask.count).toDouble()/speed - chunkmaster.logger.info(""+etaSeconds) val hours: Int = (etaSeconds/3600).toInt() val minutes: Int = ((etaSeconds % 3600) / 60).toInt() val seconds: Int = (etaSeconds % 60).toInt() From ecc3165f1f85f747d9466fb32bb4621aaf8f6138 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 8 Feb 2020 13:22:32 +0100 Subject: [PATCH 5/9] Add delay to starting of generation tasks Add a delay to the starts of the tasks so that they don't all start on the same tick. To completely avoid two tasks being executed on the same tick, the "period" config option should be set to the expected number of tasks or higher. --- .../lib/generation/GenerationManager.kt | 40 +++++++++++++------ .../lib/generation/GenerationTask.kt | 3 +- .../lib/generation/GenerationTaskSpigot.kt | 7 +++- .../resources/i18n/DEFAULT.i18n.properties | 1 + src/main/resources/i18n/de.i18n.properties | 1 + src/main/resources/i18n/en.i18n.properties | 1 + 6 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index b005834..fffa129 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -2,6 +2,7 @@ package net.trivernis.chunkmaster.lib.generation import io.papermc.lib.PaperLib import net.trivernis.chunkmaster.Chunkmaster +import org.bukkit.Chunk import org.bukkit.Server import org.bukkit.World @@ -83,13 +84,14 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server center: ChunkCoordinates, last: ChunkCoordinates, id: Int, - stopAfter: Int = -1 + stopAfter: Int = -1, + delay: Long = 200L ) { if (!paused) { chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_FOR_WORLD", world.name)) val generationTask = createGenerationTask(world, center, last, stopAfter) val task = server.scheduler.runTaskTimer( - chunkmaster, generationTask, 200, // 10 sec delay + chunkmaster, generationTask, delay, // 10 sec delay chunkmaster.config.getLong("generation.period") ) tasks.add(RunningTaskEntry(id, task, generationTask)) @@ -149,11 +151,14 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server * Stops all generation tasks */ fun stopAll() { - saveProgress() val removalSet = HashSet() for (task in tasks) { - task.generationTask.cancel() + val lastChunk = task.generationTask.lastChunkCoords + val id = task.id + chunkmaster.logger.info(chunkmaster.langManager.getLocalized("SAVING_TASK_PROGRESS", task.id)) + saveProgressToDatabase(lastChunk, id) task.task.cancel() + task.generationTask.cancel() if (task.task.isCancelled) { removalSet.add(task) } @@ -167,7 +172,9 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server */ fun startAll() { chunkmaster.sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) { res -> + var count = 0 while (res.next()) { + count++ try { val id = res.getInt("id") val world = server.getWorld(res.getString("world")) @@ -175,7 +182,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z")) val stopAfter = res.getInt("stop_after") if (this.tasks.find { it.id == id } == null) { - resumeTask(world!!, center, last, id, stopAfter) + resumeTask(world!!, center, last, id, stopAfter, 200L + count) } } catch (error: NullPointerException) { chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TASK_LOAD_FAILED", res.getInt("id"))) @@ -238,20 +245,27 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server speed, genTask.lastChunk.x, genTask.lastChunk.z)) - chunkmaster.sqliteManager.executeStatement( - """ - UPDATE generation_tasks SET last_x = ?, last_z = ? - WHERE id = ? - """.trimIndent(), - HashMap(mapOf(1 to genTask.lastChunk.x, 2 to genTask.lastChunk.z, 3 to task.id)), - null - ) + saveProgressToDatabase(genTask.lastChunkCoords, task.id) } catch (error: Exception) { chunkmaster.logger.warning(chunkmaster.langManager.getLocalized("TASK_SAVE_FAILED", error.toString())) } } } + /** + * Saves the generation progress to the database + */ + private fun saveProgressToDatabase(lastChunk: ChunkCoordinates, id: Int) { + chunkmaster.sqliteManager.executeStatement( + """ + UPDATE generation_tasks SET last_x = ?, last_z = ? + WHERE id = ? + """.trimIndent(), + HashMap(mapOf(1 to lastChunk.x, 2 to lastChunk.z, 3 to id)), + null + ) + } + /** * Creates a new generation task. This method is used to create a task depending * on the server type (Paper/Spigot). diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt index 92f3ea5..7a2f1bd 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt @@ -17,7 +17,8 @@ abstract class GenerationTask(plugin: Chunkmaster, centerChunk: ChunkCoordinates protected val spiral: Spiral = Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z)) protected val loadedChunks: HashSet = HashSet() - protected var lastChunkCoords = ChunkCoordinates(startChunk.x, startChunk.z) + var lastChunkCoords = ChunkCoordinates(startChunk.x, startChunk.z) + protected set protected val chunkSkips = plugin.config.getInt("generation.chunk-skips-per-step") protected val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold") protected val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks") diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt index cb0f9b1..6f93203 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt @@ -2,6 +2,7 @@ package net.trivernis.chunkmaster.lib.generation import net.trivernis.chunkmaster.Chunkmaster import org.bukkit.World +import java.lang.Exception class GenerationTaskSpigot( private val plugin: Chunkmaster, override val world: World, @@ -62,7 +63,11 @@ class GenerationTaskSpigot( override fun cancel() { for (chunk in loadedChunks) { if (chunk.isLoaded) { - chunk.unload(true) + try { + chunk.unload(true) + } catch (e: Exception) { + plugin.logger.severe(e.toString()) + } } } } diff --git a/src/main/resources/i18n/DEFAULT.i18n.properties b/src/main/resources/i18n/DEFAULT.i18n.properties index a692811..12530dd 100644 --- a/src/main/resources/i18n/DEFAULT.i18n.properties +++ b/src/main/resources/i18n/DEFAULT.i18n.properties @@ -35,6 +35,7 @@ NO_PERMISSION = §cYou do not have the permission for this command! SUBCOMMAND_NOT_FOUND = §cSubcommand §2%s §cnot found! STOPPING_ALL_TASKS = Stopping all generation tasks... +SAVING_TASK_PROGRESS = Saving the progress of task #%s to the database... DB_INIT = Initializing database... DB_INIT_FINISHED = Database fully initialized. DB_INIT_EROR = Failed to init database: %s. diff --git a/src/main/resources/i18n/de.i18n.properties b/src/main/resources/i18n/de.i18n.properties index 307800b..78e0547 100644 --- a/src/main/resources/i18n/de.i18n.properties +++ b/src/main/resources/i18n/de.i18n.properties @@ -35,6 +35,7 @@ NO_PERMISSION = §cDu hast nicht die Rechte für diesen Befehl! SUBCOMMAND_NOT_FOUND = §cUnteraktion §2%s §cwurde nicht gefunden! STOPPING_ALL_TASKS = Stoppt alle Aufgaben... +SAVING_TASK_PROGRESS = Speichert den Fortschritt der Aufgabe #%s in der Datenbank... DB_INIT = Initialisiere Datenbank... DB_INIT_FINISHED = Die Datenbank wurde initialisiert. DB_INIT_EROR = Fehler beim Initalisieren der Datenbank: %s. diff --git a/src/main/resources/i18n/en.i18n.properties b/src/main/resources/i18n/en.i18n.properties index a692811..12530dd 100644 --- a/src/main/resources/i18n/en.i18n.properties +++ b/src/main/resources/i18n/en.i18n.properties @@ -35,6 +35,7 @@ NO_PERMISSION = §cYou do not have the permission for this command! SUBCOMMAND_NOT_FOUND = §cSubcommand §2%s §cnot found! STOPPING_ALL_TASKS = Stopping all generation tasks... +SAVING_TASK_PROGRESS = Saving the progress of task #%s to the database... DB_INIT = Initializing database... DB_INIT_FINISHED = Database fully initialized. DB_INIT_EROR = Failed to init database: %s. From a09bebb6fac76391b118945fd77fdaea531f8a0d Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 8 Feb 2020 14:28:54 +0100 Subject: [PATCH 6/9] Update README --- README.md | 28 ++++++++++++++++++++++++---- build.gradle | 1 - 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9470446..2c1f8f0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,19 @@ and resumes when the server is empty again. The generation also auto-resumes aft restart. The plugin tracks the ticks per second and pauses the generation when the tps is lower than 2 (configurable). -## Commands +## Built with + +- [Gradle](https://gradle.org/) - Dependency Management and Build Tool +- [Sqlite JDBC](https://bitbucket.org/xerial/sqlite-jdbc/) - Database Driver for JDBC +- [bStats](https://bstats.org/) - Statistical Insights + +## Installing + +Just download the jar from the latest release and place it into the servers plugins folder. + +## Usage and Configuration + +### Commands All features can be accessed with the command `/chunkmaster` or the aliases `/chm`, `chunkm`, `cmaster`. @@ -18,7 +30,7 @@ All features can be accessed with the command `/chunkmaster` or the aliases `/ch - `/chunkmaster reload` Reloads the configuration file. - `/chunkmaster tpchunk ` Teleports you to the specified chunk coordinates. -## Config +### Config ```yaml @@ -83,8 +95,16 @@ generation: pause-on-join: true ``` -## Spigot and Paper +### Spigot and Paper The plugin works on spigot and paper servers but is significantly faster on paper servers (because it profits from asynchronous chunk loading an the better implementation of the -isChunkGenerated method). \ No newline at end of file +isChunkGenerated method). + +## License + +This project is licensed under the GPLv3.0 License - see the [License.md](https://github.com/Trivernis/spigot-chunkmaster/blob/master/LICENSE) for details. + +## bStats + +[![Plugin statistics](https://bstats.org/signatures/bukkit/chunkmaster.svg)](https://bstats.org/plugin/bukkit/Chunkmaster/5639) \ No newline at end of file diff --git a/build.gradle b/build.gradle index e9148c8..a24dac6 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,6 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" testCompile group: 'junit', name: 'junit', version: '4.12' - //compileOnly "org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT" compileOnly "com.destroystokyo.paper:paper-api:1.14.4-R0.1-SNAPSHOT" compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' compile "io.papermc:paperlib:1.0.2" From bdac496ce2914ef0e79a7a6970c5961c5025472e Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 11 Feb 2020 12:26:38 +0100 Subject: [PATCH 7/9] Fix unique world sql error Fix error thrown by unique constrain for the world name in the sql table. The error occurs because the data from the database is loaded lazily after several seconds. If a task was started before that it wouldn't be checked against the existing tasks since they haven't been loaded yet. Fixed by loading the tasks if none exists when the generate command is invoked. --- .../chunkmaster/lib/generation/GenerationManager.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index fffa129..830db4d 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -12,6 +12,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server val pausedTasks: HashSet = HashSet() val allTasks: HashSet get() { + if (this.tasks.isEmpty() && this.pausedTasks.isEmpty()) { + this.startAll() + if (!server.onlinePlayers.isEmpty()) { + this.pauseAll() + } + } val all = HashSet() all.addAll(pausedTasks) all.addAll(tasks) @@ -141,8 +147,9 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server saveProgress() // save progress every 30 seconds }, 600, 600) server.scheduler.runTaskLater(chunkmaster, Runnable { - if (server.onlinePlayers.isEmpty()) { - startAll() // run startAll after 10 seconds if empty + this.startAll() + if (!server.onlinePlayers.isEmpty()) { + this.pauseAll() } }, 600) } From 832b1c4b5528e26b7a5b81373ca8db54b3b01889 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 11 Feb 2020 15:29:47 +0100 Subject: [PATCH 8/9] Add command to set and get the center - Add getCenter command to get the center of the world used for generation - Add setCenter to set the world center for generation tasks. If there is already a task running for the world the center for that task will not be affected. - Fix SQL problem with multiple queries at the same time creating new connections - Add translations for new commands - Add Examples to Readme - Add new table to safe world centers - Add query to Generation manager to get the center of the world for new generation tasks --- README.md | 23 +++++ .../chunkmaster/commands/CmdGetCenter.kt | 65 ++++++++++++++ .../chunkmaster/commands/CmdSetCenter.kt | 85 +++++++++++++++++++ .../commands/CommandChunkmaster.kt | 8 +- .../chunkmaster/lib/SqliteManager.kt | 25 +++++- .../lib/generation/GenerationManager.kt | 57 ++++++++++++- .../resources/i18n/DEFAULT.i18n.properties | 7 +- src/main/resources/i18n/de.i18n.properties | 7 +- src/main/resources/i18n/en.i18n.properties | 7 +- src/main/resources/plugin.yml | 8 ++ 10 files changed, 284 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt diff --git a/README.md b/README.md index 2c1f8f0..a52e6c9 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,29 @@ All features can be accessed with the command `/chunkmaster` or the aliases `/ch - `/chunkmaster resume` Resumes all paused generation tasks. - `/chunkmaster reload` Reloads the configuration file. - `/chunkmaster tpchunk ` Teleports you to the specified chunk coordinates. +- `/ setCenter [] ` - sets the center chunk of the world +- `/ getCenter []` - returns the center chunk of the world + +#### Examples +**Generate a 100 chunks * 100 chunks square around the spawn:** + +`/chm generate [world] 100 diameter` + +**Generate a 100 blocks * 100 blocks square around the spawn:** + +`/chm generate [world] 50 blockradius` + +**Generate 100 Blocks in every direction from the spawn:** + +`/chm generate [world] 100 blockradius` + +**Generate 200 Chunks in every direction from the spawn:** + +`/chm generate [world] 200 radius` + +**Generate 1000 Chunks in total around the spawn:** + +`/chm generate [world] 1000` ### Config diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt new file mode 100644 index 0000000..b763cd3 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt @@ -0,0 +1,65 @@ +package net.trivernis.chunkmaster.commands + +import net.trivernis.chunkmaster.Chunkmaster +import net.trivernis.chunkmaster.lib.Subcommand +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class CmdGetCenter(private val chunkmaster: Chunkmaster): Subcommand { + override val name = "getCenter"; + + override fun onTabComplete( + sender: CommandSender, + command: Command, + alias: String, + args: List + ): MutableList { + if (args.size == 1) { + return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } + .map {it.name}.toMutableList() + } + return emptyList().toMutableList() + } + + override fun execute(sender: CommandSender, args: List): Boolean { + val worldName: String = if (sender is Player) { + if (args.isNotEmpty()) { + args[0] + } else { + sender.world.name; + } + } else { + if (args.isEmpty()) { + sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NAME_REQUIRED")) + return false + } else { + args[0] + } + } + if (chunkmaster.generationManager.worldCenters.isEmpty()) { + chunkmaster.generationManager.loadWorldCenters() { + sendCenterInfo(sender, worldName) + } + return true + } + sendCenterInfo(sender, worldName) + return true + } + + /** + * Sends the center information + */ + private fun sendCenterInfo(sender: CommandSender, worldName: String) { + var center = chunkmaster.generationManager.worldCenters[worldName] + if (center == null) { + val world = sender.server.worlds.find { it.name == worldName } + if (world == null) { + sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName)) + return + } + center = Pair(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) + } + sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_INFO", worldName, center.first, center.second)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt new file mode 100644 index 0000000..eb09826 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt @@ -0,0 +1,85 @@ +package net.trivernis.chunkmaster.commands + +import net.trivernis.chunkmaster.Chunkmaster +import net.trivernis.chunkmaster.lib.Subcommand +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class CmdSetCenter(private val chunkmaster: Chunkmaster): Subcommand { + override val name = "setCenter"; + + override fun onTabComplete( + sender: CommandSender, + command: Command, + alias: String, + args: List + ): MutableList { + if (args.size == 1) { + if (args[0].toIntOrNull() == null) { + return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } + .map {it.name}.toMutableList() + } + } + return emptyList().toMutableList(); + } + + override fun execute(sender: CommandSender, args: List): Boolean { + val world: String + val centerX: Int + val centerZ: Int + + if (args.size < 2) { + sender.sendMessage(chunkmaster.langManager.getLocalized("TOO_FEW_ARGUMENTS")) + return false + } + if (sender is Player) { + if (args.size == 2) { + world = sender.world.name + if (args[0].toIntOrNull() == null || args[1].toIntOrNull() == null) { + sender.sendMessage(chunkmaster.langManager.getLocalized("COORD_INVALID", args[0], args[1])) + return false + } + centerX = args[0].toInt() + centerZ = args[1].toInt() + } else { + if (!validateThreeArgs(sender, args)) { + return false + } + world = args[0] + centerX = args[1].toInt() + centerZ = args[2].toInt() + } + } else { + if (args.size < 3) { + sender.sendMessage(chunkmaster.langManager.getLocalized("TOO_FEW_ARGUMENTS")) + return false + } else { + if (!validateThreeArgs(sender, args)) { + return false + } + world = args[0] + centerX = args[1].toInt() + centerZ = args[2].toInt() + } + } + chunkmaster.generationManager.updateWorldCenter(world, Pair(centerX, centerZ)) + sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_UPDATED", world, centerX, centerZ)) + return true + } + + /** + * Validates the command values with three arguments + */ + private fun validateThreeArgs(sender: CommandSender, args: List): Boolean { + return if (sender.server.worlds.none { it.name == args[0] }) { + sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", args[0])) + false + } else if (args[1].toIntOrNull() == null || args[2].toIntOrNull() == null) { + sender.sendMessage(chunkmaster.langManager.getLocalized("COORD_INVALID", args[1], args[2])) + false + } else { + true + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt index bf05ce2..30a0bdb 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt @@ -40,7 +40,7 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve */ override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { if (args.isNotEmpty()) { - if (sender.hasPermission("chunkmaster.${args[0]}")) { + if (sender.hasPermission("chunkmaster.${args[0].toLowerCase()}")) { return if (commands.containsKey(args[0])) { commands[args[0]]!!.execute(sender, args.slice(1 until args.size)) } else { @@ -80,5 +80,11 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve val cmdTpChunk = CmdTpChunk(chunkmaster) commands[cmdTpChunk.name] = cmdTpChunk + + val cmdSetCenter = CmdSetCenter(chunkmaster) + commands[cmdSetCenter.name] = cmdSetCenter + + val cmdGetCenter = CmdGetCenter(chunkmaster) + commands[cmdGetCenter.name] = cmdGetCenter } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt index 507f97d..00dcd5b 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt @@ -22,19 +22,33 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"), Pair("stop_after", "integer DEFAULT -1") ) + ), + Pair( + "world_properties", + listOf( + Pair("name", "text PRIMARY KEY"), + Pair("center_x", "integer NOT NULL DEFAULT 0"), + Pair("center_z", "integer NOT NULL DEFAULT 0") + ) ) ) private val needUpdate = HashSet>>() private val needCreation = HashSet() + private var connection: Connection? = null + private var activeTasks = 0 /** * Returns the connection to the database */ fun getConnection(): Connection? { + if (this.connection != null) { + return this.connection + } try { Class.forName("org.sqlite.JDBC") - return DriverManager.getConnection("jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + + this.connection = DriverManager.getConnection("jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + "${chunkmaster.config.getString("database.filename")}") + return this.connection } catch (e: Exception) { chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR")) chunkmaster.logger.severe(e.message) @@ -79,6 +93,7 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { */ fun executeStatement(sql: String, values: HashMap, callback: ((ResultSet) -> Unit)?) { val connection = getConnection() + activeTasks++ if (connection != null) { try { val statement = connection.prepareStatement(sql) @@ -92,10 +107,14 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { } statement.close() } catch (e: Exception) { - chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("SQL_ERROR", e.message!!)) + chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("SQL_ERROR", e.toString())) chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) } finally { - connection.close() + activeTasks-- + if (activeTasks == 0) { + connection.close() + this.connection = null + } } } else { chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("NO_DATABASE_CONNECTION")) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index 830db4d..f822620 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -10,9 +10,13 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server val tasks: HashSet = HashSet() val pausedTasks: HashSet = HashSet() + val worldCenters: HashMap> = HashMap() val allTasks: HashSet get() { if (this.tasks.isEmpty() && this.pausedTasks.isEmpty()) { + if (this.worldCenters.isEmpty()) { + this.loadWorldCenters() + } this.startAll() if (!server.onlinePlayers.isEmpty()) { this.pauseAll() @@ -32,7 +36,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server fun addTask(world: World, stopAfter: Int = -1): Int { val foundTask = allTasks.find { it.generationTask.world == world } if (foundTask == null) { - val centerChunk = ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) + val centerChunk = if (worldCenters[world.name] == null) { + ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) + } else { + val center = worldCenters[world.name]!! + ChunkCoordinates(center.first, center.second) + } val generationTask = createGenerationTask(world, centerChunk, centerChunk, stopAfter) chunkmaster.sqliteManager.executeStatement( @@ -147,6 +156,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server saveProgress() // save progress every 30 seconds }, 600, 600) server.scheduler.runTaskLater(chunkmaster, Runnable { + this.loadWorldCenters() this.startAll() if (!server.onlinePlayers.isEmpty()) { this.pauseAll() @@ -222,6 +232,51 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server startAll() } + /** + * Overload that doesn't need an argument + */ + fun loadWorldCenters() { + loadWorldCenters(null) + } + + /** + * Loads the world centers from the database + */ + fun loadWorldCenters(cb: (() -> Unit)?) { + chunkmaster.sqliteManager.executeStatement("SELECT * FROM world_properties", HashMap()) { + while (it.next()) { + worldCenters[it.getString("name")] = Pair(it.getInt("center_x"), it.getInt("center_z")) + } + cb?.invoke() + } + } + + /** + * Updates the center of a world + */ + fun updateWorldCenter(worldName: String, center: Pair) { + chunkmaster.sqliteManager.executeStatement("SELECT * FROM world_properties WHERE name = ?", HashMap(mapOf(1 to worldName))) { + if (it.next()) { + chunkmaster.sqliteManager.executeStatement("UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?", HashMap( + mapOf( + 1 to center.first, + 2 to center.second, + 3 to worldName + ) + ), null) + } else { + chunkmaster.sqliteManager.executeStatement("INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)", HashMap( + mapOf( + 1 to worldName, + 2 to center.first, + 3 to center.second + ) + ), null) + } + } + worldCenters[worldName] = center + } + /** * Saves the task progress */ diff --git a/src/main/resources/i18n/DEFAULT.i18n.properties b/src/main/resources/i18n/DEFAULT.i18n.properties index 12530dd..1b70fdc 100644 --- a/src/main/resources/i18n/DEFAULT.i18n.properties +++ b/src/main/resources/i18n/DEFAULT.i18n.properties @@ -46,4 +46,9 @@ NO_DATABASE_CONNECTION = §cCould not execute sql: No database connection. CREATE_TABLE_DEFINITION = Created table %s with definition %s. TABLE_CREATE_ERROR = §cError when creation table %s. UPDATE_TABLE_DEFINITION = Updated table %s with sql %s. -UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. \ No newline at end of file +UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. + +TOO_FEW_ARGUMENTS = §cYou did not provide enough arguments. +COORD_INVALID = §cThe provided coordinate ('%s', '%s') is invalid! +CENTER_UPDATED = §9The center for world §2%s §9has been set to §2(%s, %s)§9. +CENTER_INFO = §9The center for world §2%s §9is §2(%s, %s)§9. \ No newline at end of file diff --git a/src/main/resources/i18n/de.i18n.properties b/src/main/resources/i18n/de.i18n.properties index 78e0547..02b0995 100644 --- a/src/main/resources/i18n/de.i18n.properties +++ b/src/main/resources/i18n/de.i18n.properties @@ -46,4 +46,9 @@ NO_DATABASE_CONNECTION = §cSql konnte nicht ausgeführt werden: Keine Datenbank CREATE_TABLE_DEFINITION = Tabelle %s mit Definition %s wurde erstellt. TABLE_CREATE_ERROR = §cFehler beim erstellen der Tabelle %s. UPDATE_TABLE_DEFINITION = Tabelle %s wurde mit sql %s geupdated. -UPDATE_TABLE_FAILED = Fehler beim Updaten der Tabelle %s mit sql %s. \ No newline at end of file +UPDATE_TABLE_FAILED = Fehler beim Updaten der Tabelle %s mit sql %s. + +TOO_FEW_ARGUMENTS = §cDu hast nicht genug arguments angegeben. +COORD_INVALID = §cDie Koordinate ('%s', '%s') ist ungültig! +CENTER_UPDATED = §9Die Mitte der Welt §2%s §9wurde auf §2(%s, %s)§9 gesetzt. +CENTER_INFO = §9Die Mitte der Welt §2%s §9ist §2(%s, %s)§9. \ No newline at end of file diff --git a/src/main/resources/i18n/en.i18n.properties b/src/main/resources/i18n/en.i18n.properties index 12530dd..1b70fdc 100644 --- a/src/main/resources/i18n/en.i18n.properties +++ b/src/main/resources/i18n/en.i18n.properties @@ -46,4 +46,9 @@ NO_DATABASE_CONNECTION = §cCould not execute sql: No database connection. CREATE_TABLE_DEFINITION = Created table %s with definition %s. TABLE_CREATE_ERROR = §cError when creation table %s. UPDATE_TABLE_DEFINITION = Updated table %s with sql %s. -UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. \ No newline at end of file +UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. + +TOO_FEW_ARGUMENTS = §cYou did not provide enough arguments. +COORD_INVALID = §cThe provided coordinate ('%s', '%s') is invalid! +CENTER_UPDATED = §9The center for world §2%s §9has been set to §2(%s, %s)§9. +CENTER_INFO = §9The center for world §2%s §9is §2(%s, %s)§9. \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 19804bf..f61a978 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,6 +17,8 @@ commands: / resume - resumes all generation tasks / reload - reloads the configuration and restarts all tasks / tpchunk - teleports you to the chunk with the given chunk coordinates + / setCenter [] - sets the center chunk of the world + / getCenter [] - returns the center chunk of the world aliases: - chm - chunkm @@ -43,6 +45,12 @@ permissions: chunkmaster.tpchunk: description: Allows the tpchunk subcommand. default: op + chunkmaster.setcenter: + description: Allows the setCenter subcommand. + default: op + chunkmaster.getcenter: + description: Allows the getCenter subcommand. + default: op chunkmaster.chunkmaster: description: Allows Chunkmaster commands. default: op From 8953b63c2b5a0e2677371f798e50cff5b7637b1b Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 11 Apr 2020 14:12:05 +0200 Subject: [PATCH 9/9] Add integration for dynmap By integrating with dynmap the plugin can show the area of the generation task for each world via an area marker and triggers the rendering of tiles for generated chunks. --- README.md | 17 ++++ build.gradle | 6 ++ .../net/trivernis/chunkmaster/Chunkmaster.kt | 18 ++++ .../chunkmaster/commands/CmdReload.kt | 1 + .../lib/generation/GenerationTask.kt | 87 ++++++++++++++++++- .../lib/generation/GenerationTaskPaper.kt | 18 ++-- .../lib/generation/GenerationTaskSpigot.kt | 16 ++-- .../resources/i18n/DEFAULT.i18n.properties | 4 +- src/main/resources/i18n/de.i18n.properties | 4 +- src/main/resources/i18n/en.i18n.properties | 4 +- src/main/resources/plugin.yml | 3 + 11 files changed, 152 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index a52e6c9..a281f65 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,15 @@ is lower than 2 (configurable). - [Sqlite JDBC](https://bitbucket.org/xerial/sqlite-jdbc/) - Database Driver for JDBC - [bStats](https://bstats.org/) - Statistical Insights +## Features + +- Pregeneration of a specific area around the world center +- Configuration of world centers +- Integration into dynmap +- Teleportation to chunks +- Auto-Pause/Resume on player join/leave +- Highly configurable + ## Installing Just download the jar from the latest release and place it into the servers plugins folder. @@ -66,6 +75,14 @@ All features can be accessed with the command `/chunkmaster` or the aliases `/ch # For built-in support please create a PullRequest with your translation. language: en +# Actiates/deactivates the dynmap integration. +# With the setting set to 'true' the plugin tries to trigger the rendering +# of generated chunks right before unloading them. It also adds an area +# marker to the dynmap to show the area that will be pregenerated. +# The marker is removed automatically when the task is finished or canceled. +# The value should be a boolean +dynmap: true + generation: # If set to true the plugin ignores the vanilla world border and doesn't stop diff --git a/build.gradle b/build.gradle index a24dac6..66a32f9 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,11 @@ repositories { name 'CodeMc' url 'https://repo.codemc.org/repository/maven-public' } + + maven { + name 'mikeprimm' + url 'http://repo.mikeprimm.com' + } } dependencies { @@ -49,6 +54,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" testCompile group: 'junit', name: 'junit', version: '4.12' compileOnly "com.destroystokyo.paper:paper-api:1.14.4-R0.1-SNAPSHOT" + compileOnly "org.dynmap:dynmap-api:2.0" compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' compile "io.papermc:paperlib:1.0.2" compile "org.bstats:bstats-bukkit:1.5" diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index efc8916..f1d6db4 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -8,6 +8,8 @@ import net.trivernis.chunkmaster.lib.SqliteManager import org.bstats.bukkit.Metrics import org.bukkit.plugin.java.JavaPlugin import org.bukkit.scheduler.BukkitTask +import org.dynmap.DynmapAPI +import org.dynmap.DynmapCommonAPI import java.lang.Exception class Chunkmaster: JavaPlugin() { @@ -15,6 +17,8 @@ class Chunkmaster: JavaPlugin() { lateinit var generationManager: GenerationManager lateinit var langManager: LanguageManager private lateinit var tpsTask: BukkitTask + var dynmapApi: DynmapAPI? = null + private set var mspt = 20 // keep track of the milliseconds per tick private set @@ -29,6 +33,9 @@ class Chunkmaster: JavaPlugin() { langManager = LanguageManager(this) langManager.loadProperties() + + this.dynmapApi = getDynmap() + initDatabase() generationManager = GenerationManager(this, server) generationManager.init() @@ -75,6 +82,7 @@ class Chunkmaster: JavaPlugin() { config.addDefault("generation.ignore-worldborder", false) config.addDefault("database.filename", "chunkmaster.db") config.addDefault("language", "en") + config.addDefault("dynmap", true) config.options().copyDefaults(true) saveConfig() } @@ -92,4 +100,14 @@ class Chunkmaster: JavaPlugin() { logger.warning(langManager.getLocalized("DB_INIT_EROR", e.message!!)) } } + + private fun getDynmap(): DynmapAPI? { + val dynmap = server.pluginManager.getPlugin("dynmap") + return if (dynmap != null && dynmap is DynmapAPI) { + logger.info(langManager.getLocalized("PLUGIN_DETECTED", "dynmap", dynmap.dynmapVersion)) + dynmap + } else { + null + } + } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt index 4a38fa6..1503770 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt @@ -27,6 +27,7 @@ class CmdReload(private val chunkmaster: Chunkmaster): Subcommand { chunkmaster.generationManager.stopAll() chunkmaster.reloadConfig() chunkmaster.generationManager.startAll() + chunkmaster.langManager.loadProperties() sender.sendMessage(chunkmaster.langManager.getLocalized("CONFIG_RELOADED")) return true } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt index 7a2f1bd..8436a92 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt @@ -3,16 +3,20 @@ package net.trivernis.chunkmaster.lib.generation import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.lib.Spiral import org.bukkit.Chunk +import org.bukkit.Location import org.bukkit.World +import kotlin.math.* + /** * Interface for generation tasks. */ -abstract class GenerationTask(plugin: Chunkmaster, centerChunk: ChunkCoordinates, startChunk: ChunkCoordinates) : +abstract class GenerationTask(plugin: Chunkmaster, private val centerChunk: ChunkCoordinates, startChunk: ChunkCoordinates) : Runnable { + abstract val stopAfter: Int abstract val world: World abstract val count: Int - abstract val endReached: Boolean + abstract var endReached: Boolean protected val spiral: Spiral = Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z)) @@ -23,11 +27,15 @@ abstract class GenerationTask(plugin: Chunkmaster, centerChunk: ChunkCoordinates protected val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold") protected val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks") protected val chunksPerStep = plugin.config.getInt("generation.chunks-per-step") - protected val ignoreWorldborder = plugin.config.getBoolean("generation.ignore-worldborder") - + protected val dynmapIntegration = plugin.config.getBoolean("dynmap") + protected val dynmap = plugin.dynmapApi protected var endReachedCallback: ((GenerationTask) -> Unit)? = null private set + private val markerId = "chunkmaster_genarea" + private val markerName = "Chunkmaster Generation Area" + private val ignoreWorldborder = plugin.config.getBoolean("generation.ignore-worldborder") + abstract override fun run() abstract fun cancel() @@ -56,6 +64,77 @@ abstract class GenerationTask(plugin: Chunkmaster, centerChunk: ChunkCoordinates || (stopAfter in 1..count) } + /** + * Unloads all chunks that have been loaded + */ + protected fun unloadLoadedChunks() { + for (chunk in loadedChunks) { + if (chunk.isLoaded) { + chunk.unload(true) + } + if (dynmapIntegration) { + dynmap?.triggerRenderOfVolume(chunk.getBlock(0, 0, 0).location, chunk.getBlock(15, 255, 15).location) + } + } + + loadedChunks.clear() + } + + /** + * Updates the dynmap marker for the generation radius + */ + protected fun updateDynmapMarker(clear: Boolean = false) { + val markerSet = dynmap?.markerAPI?.getMarkerSet("markers") + var marker = markerSet?.findAreaMarker(markerId) + if (clear) { + marker?.deleteMarker() + } else if (dynmapIntegration && stopAfter > 0) { + val (topLeft, bottomRight) = this.getAreaCorners() + if (marker != null) { + marker.setCornerLocations( + doubleArrayOf((topLeft.x * 16).toDouble(), (bottomRight.x * 16).toDouble()), + doubleArrayOf((topLeft.z * 16).toDouble(), (bottomRight.z * 16).toDouble()) + ) + } else { + marker = markerSet?.createAreaMarker( + markerId, + markerName, + false, + world.name, + doubleArrayOf((topLeft.x * 16).toDouble(), (bottomRight.x * 16).toDouble()), + doubleArrayOf((topLeft.z * 16).toDouble(), (bottomRight.z * 16).toDouble()), + true + ) + } + marker?.setFillStyle(.0, 0) + marker?.setLineStyle(2, 1.0, 0x0000FF) + } + } + + /** + * Returns an approximation of cornders of the generation area + */ + protected fun getAreaCorners(): Pair { + val width = sqrt(stopAfter.toFloat()) + return Pair( + ChunkCoordinates(centerChunk.x - floor(width/2).toInt(), centerChunk.z - floor(width/2).toInt()), + ChunkCoordinates(centerChunk.x + ceil(width/2).toInt(), centerChunk.z + ceil(width/2).toInt()) + ) + } + + /** + * Handles the invocation of the end reached callback and additional logic + */ + protected fun setEndReached() { + endReached = true + endReachedCallback?.invoke(this) + updateDynmapMarker(true) + if (dynmapIntegration) { + val (topLeft, bottomRight) = this.getAreaCorners() + dynmap?.triggerRenderOfVolume(topLeft.getCenterLocation(world), bottomRight.getCenterLocation(world)) + } + } + /** * Registers end reached callback */ diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt index 7342df1..a8e20bc 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt @@ -18,7 +18,10 @@ class GenerationTaskPaper( override var count = 0 private set override var endReached: Boolean = false - private set + + init { + updateDynmapMarker() + } /** * Runs the generation task. Every Iteration the next chunk will be generated if @@ -28,16 +31,10 @@ class GenerationTaskPaper( override fun run() { if (plugin.mspt < msptThreshold) { // pause when tps < 2 if (loadedChunks.size > maxLoadedChunks) { - for (chunk in loadedChunks) { - if (chunk.isLoaded) { - chunk.unload(true) - } - } - loadedChunks.clear() + unloadLoadedChunks() } else if (pendingChunks.size < maxPendingChunks) { // if more than 10 chunks are pending, wait. if (borderReached()) { - endReached = true - endReachedCallback?.invoke(this) + setEndReached() return } @@ -73,13 +70,14 @@ class GenerationTaskPaper( * This unloads all chunks that were generated but not unloaded yet. */ override fun cancel() { + updateDynmapMarker(true) unloadAllChunks() } /** * Cancels all pending chunks and unloads all loaded chunks. */ - fun unloadAllChunks() { + private fun unloadAllChunks() { for (pendingChunk in pendingChunks) { if (pendingChunk.isDone) { loadedChunks.add(pendingChunk.get()) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt index 6f93203..bf5a36f 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt @@ -14,7 +14,10 @@ class GenerationTaskSpigot( override var count = 0 private set override var endReached: Boolean = false - private set + + init { + updateDynmapMarker() + } /** * Runs the generation task. Every Iteration the next chunk will be generated if @@ -24,16 +27,10 @@ class GenerationTaskSpigot( override fun run() { if (plugin.mspt < msptThreshold) { // pause when tps < 2 if (loadedChunks.size > maxLoadedChunks) { - for (chunk in loadedChunks) { - if (chunk.isLoaded) { - chunk.unload(true) - } - } - loadedChunks.clear() + unloadLoadedChunks() } else { if (borderReached()) { - endReached = true - endReachedCallback?.invoke(this) + setEndReached() return } @@ -70,5 +67,6 @@ class GenerationTaskSpigot( } } } + updateDynmapMarker(true) } } \ No newline at end of file diff --git a/src/main/resources/i18n/DEFAULT.i18n.properties b/src/main/resources/i18n/DEFAULT.i18n.properties index 1b70fdc..fac0f79 100644 --- a/src/main/resources/i18n/DEFAULT.i18n.properties +++ b/src/main/resources/i18n/DEFAULT.i18n.properties @@ -51,4 +51,6 @@ UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. TOO_FEW_ARGUMENTS = §cYou did not provide enough arguments. COORD_INVALID = §cThe provided coordinate ('%s', '%s') is invalid! CENTER_UPDATED = §9The center for world §2%s §9has been set to §2(%s, %s)§9. -CENTER_INFO = §9The center for world §2%s §9is §2(%s, %s)§9. \ No newline at end of file +CENTER_INFO = §9The center for world §2%s §9is §2(%s, %s)§9. + +PLUGIN_DETECTED = Detected %s version %s \ No newline at end of file diff --git a/src/main/resources/i18n/de.i18n.properties b/src/main/resources/i18n/de.i18n.properties index 02b0995..1307e88 100644 --- a/src/main/resources/i18n/de.i18n.properties +++ b/src/main/resources/i18n/de.i18n.properties @@ -51,4 +51,6 @@ UPDATE_TABLE_FAILED = Fehler beim Updaten der Tabelle %s mit sql %s. TOO_FEW_ARGUMENTS = §cDu hast nicht genug arguments angegeben. COORD_INVALID = §cDie Koordinate ('%s', '%s') ist ungültig! CENTER_UPDATED = §9Die Mitte der Welt §2%s §9wurde auf §2(%s, %s)§9 gesetzt. -CENTER_INFO = §9Die Mitte der Welt §2%s §9ist §2(%s, %s)§9. \ No newline at end of file +CENTER_INFO = §9Die Mitte der Welt §2%s §9ist §2(%s, %s)§9. + +PLUGIN_DETECTED = Plugin %s in der Version %s gefunden! \ No newline at end of file diff --git a/src/main/resources/i18n/en.i18n.properties b/src/main/resources/i18n/en.i18n.properties index 1b70fdc..fac0f79 100644 --- a/src/main/resources/i18n/en.i18n.properties +++ b/src/main/resources/i18n/en.i18n.properties @@ -51,4 +51,6 @@ UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. TOO_FEW_ARGUMENTS = §cYou did not provide enough arguments. COORD_INVALID = §cThe provided coordinate ('%s', '%s') is invalid! CENTER_UPDATED = §9The center for world §2%s §9has been set to §2(%s, %s)§9. -CENTER_INFO = §9The center for world §2%s §9is §2(%s, %s)§9. \ No newline at end of file +CENTER_INFO = §9The center for world §2%s §9is §2(%s, %s)§9. + +PLUGIN_DETECTED = Detected %s version %s \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f61a978..8bf099a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,6 +5,9 @@ description: Chunk commands plugin. author: Trivernis website: trivernis.net api-version: '1.14' +database: true +softdepend: + - dynmap commands: chunkmaster: description: Main command