Merge pull request #29 from Trivernis/develop

Release Beta 0.15
feature/stats-command v0.15-beta
Trivernis 4 years ago committed by GitHub
commit 36394d1768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,7 +6,28 @@ 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 restart. The plugin tracks the ticks per second and pauses the generation when the tps
is lower than 2 (configurable). 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
## 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.
## Usage and Configuration
### Commands
All features can be accessed with the command `/chunkmaster` or the aliases `/chm`, `chunkm`, `cmaster`. All features can be accessed with the command `/chunkmaster` or the aliases `/chm`, `chunkm`, `cmaster`.
@ -17,8 +38,31 @@ All features can be accessed with the command `/chunkmaster` or the aliases `/ch
- `/chunkmaster resume` Resumes all paused generation tasks. - `/chunkmaster resume` Resumes all paused generation tasks.
- `/chunkmaster reload` Reloads the configuration file. - `/chunkmaster reload` Reloads the configuration file.
- `/chunkmaster tpchunk <X> <Z>` Teleports you to the specified chunk coordinates. - `/chunkmaster tpchunk <X> <Z>` Teleports you to the specified chunk coordinates.
- `/<command> setCenter [<world>] <chunkX> <chunkZ>` - sets the center chunk of the world
- `/<command> getCenter [<world>]` - 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`
## Config **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
```yaml ```yaml
@ -31,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. # For built-in support please create a PullRequest with your translation.
language: en 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 <true/false>
dynmap: true
generation: generation:
# If set to true the plugin ignores the vanilla world border and doesn't stop # If set to true the plugin ignores the vanilla world border and doesn't stop
@ -83,8 +135,16 @@ generation:
pause-on-join: true 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 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 (because it profits from asynchronous chunk loading an the better implementation of the
isChunkGenerated method). 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)

@ -22,7 +22,7 @@ idea {
} }
group "net.trivernis" group "net.trivernis"
version "0.14-beta" version "0.15-beta"
sourceCompatibility = 1.8 sourceCompatibility = 1.8
@ -42,14 +42,19 @@ repositories {
name 'CodeMc' name 'CodeMc'
url 'https://repo.codemc.org/repository/maven-public' url 'https://repo.codemc.org/repository/maven-public'
} }
maven {
name 'mikeprimm'
url 'http://repo.mikeprimm.com'
}
} }
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12' 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" 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 group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0'
compile "io.papermc:paperlib:1.0.2" compile "io.papermc:paperlib:1.0.2"
compile "org.bstats:bstats-bukkit:1.5" compile "org.bstats:bstats-bukkit:1.5"

@ -8,6 +8,8 @@ import net.trivernis.chunkmaster.lib.SqliteManager
import org.bstats.bukkit.Metrics import org.bstats.bukkit.Metrics
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
import org.dynmap.DynmapAPI
import org.dynmap.DynmapCommonAPI
import java.lang.Exception import java.lang.Exception
class Chunkmaster: JavaPlugin() { class Chunkmaster: JavaPlugin() {
@ -15,6 +17,8 @@ class Chunkmaster: JavaPlugin() {
lateinit var generationManager: GenerationManager lateinit var generationManager: GenerationManager
lateinit var langManager: LanguageManager lateinit var langManager: LanguageManager
private lateinit var tpsTask: BukkitTask private lateinit var tpsTask: BukkitTask
var dynmapApi: DynmapAPI? = null
private set
var mspt = 20 // keep track of the milliseconds per tick var mspt = 20 // keep track of the milliseconds per tick
private set private set
@ -29,6 +33,9 @@ class Chunkmaster: JavaPlugin() {
langManager = LanguageManager(this) langManager = LanguageManager(this)
langManager.loadProperties() langManager.loadProperties()
this.dynmapApi = getDynmap()
initDatabase() initDatabase()
generationManager = GenerationManager(this, server) generationManager = GenerationManager(this, server)
generationManager.init() generationManager.init()
@ -75,6 +82,7 @@ class Chunkmaster: JavaPlugin() {
config.addDefault("generation.ignore-worldborder", false) config.addDefault("generation.ignore-worldborder", false)
config.addDefault("database.filename", "chunkmaster.db") config.addDefault("database.filename", "chunkmaster.db")
config.addDefault("language", "en") config.addDefault("language", "en")
config.addDefault("dynmap", true)
config.options().copyDefaults(true) config.options().copyDefaults(true)
saveConfig() saveConfig()
} }
@ -92,4 +100,14 @@ class Chunkmaster: JavaPlugin() {
logger.warning(langManager.getLocalized("DB_INIT_EROR", e.message!!)) 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
}
}
} }

@ -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<String>
): MutableList<String> {
if (args.size == 1) {
return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 }
.map {it.name}.toMutableList()
}
return emptyList<String>().toMutableList()
}
override fun execute(sender: CommandSender, args: List<String>): 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))
}
}

@ -27,6 +27,7 @@ class CmdReload(private val chunkmaster: Chunkmaster): Subcommand {
chunkmaster.generationManager.stopAll() chunkmaster.generationManager.stopAll()
chunkmaster.reloadConfig() chunkmaster.reloadConfig()
chunkmaster.generationManager.startAll() chunkmaster.generationManager.startAll()
chunkmaster.langManager.loadProperties()
sender.sendMessage(chunkmaster.langManager.getLocalized("CONFIG_RELOADED")) sender.sendMessage(chunkmaster.langManager.getLocalized("CONFIG_RELOADED"))
return true return true
} }

@ -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<String>
): MutableList<String> {
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<String>().toMutableList();
}
override fun execute(sender: CommandSender, args: List<String>): 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<String>): 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
}
}
}

@ -40,7 +40,7 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
*/ */
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean { override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
if (args.isNotEmpty()) { if (args.isNotEmpty()) {
if (sender.hasPermission("chunkmaster.${args[0]}")) { if (sender.hasPermission("chunkmaster.${args[0].toLowerCase()}")) {
return if (commands.containsKey(args[0])) { return if (commands.containsKey(args[0])) {
commands[args[0]]!!.execute(sender, args.slice(1 until args.size)) commands[args[0]]!!.execute(sender, args.slice(1 until args.size))
} else { } else {
@ -80,5 +80,11 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
val cmdTpChunk = CmdTpChunk(chunkmaster) val cmdTpChunk = CmdTpChunk(chunkmaster)
commands[cmdTpChunk.name] = cmdTpChunk commands[cmdTpChunk.name] = cmdTpChunk
val cmdSetCenter = CmdSetCenter(chunkmaster)
commands[cmdSetCenter.name] = cmdSetCenter
val cmdGetCenter = CmdGetCenter(chunkmaster)
commands[cmdGetCenter.name] = cmdGetCenter
} }
} }

@ -1,9 +1,8 @@
package net.trivernis.chunkmaster.lib package net.trivernis.chunkmaster.lib
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import java.io.BufferedReader
import java.io.File
import java.lang.Exception import java.lang.Exception
import java.util.Properties import java.util.Properties
import java.io.*
class LanguageManager(private val plugin: Chunkmaster) { class LanguageManager(private val plugin: Chunkmaster) {
private val langProps = Properties() private val langProps = Properties()
@ -22,7 +21,7 @@ class LanguageManager(private val plugin: Chunkmaster) {
val loader = Thread.currentThread().contextClassLoader val loader = Thread.currentThread().contextClassLoader
val defaultStream = this.javaClass.getResourceAsStream("/i18n/DEFAULT.i18n.properties") val defaultStream = this.javaClass.getResourceAsStream("/i18n/DEFAULT.i18n.properties")
if (defaultStream != null) { if (defaultStream != null) {
langProps.load(defaultStream) langProps.load(getReaderForProperties(defaultStream))
defaultStream.close() defaultStream.close()
} else { } else {
plugin.logger.severe("Couldn't load default language properties.") plugin.logger.severe("Couldn't load default language properties.")
@ -31,7 +30,7 @@ class LanguageManager(private val plugin: Chunkmaster) {
try { try {
val inputStream = loader.getResourceAsStream(langFile) val inputStream = loader.getResourceAsStream(langFile)
if (inputStream != null) { if (inputStream != null) {
langProps.load(inputStream) langProps.load(getReaderForProperties(inputStream))
langFileLoaded = true langFileLoaded = true
inputStream.close() inputStream.close()
} }
@ -42,7 +41,7 @@ class LanguageManager(private val plugin: Chunkmaster) {
} else { } else {
val inputStream = this.javaClass.getResourceAsStream("/i18n/$language.i18n.properties") val inputStream = this.javaClass.getResourceAsStream("/i18n/$language.i18n.properties")
if (inputStream != null) { if (inputStream != null) {
langProps.load(inputStream) langProps.load(getReaderForProperties(inputStream))
langFileLoaded = true langFileLoaded = true
inputStream.close() inputStream.close()
} else { } else {
@ -58,4 +57,11 @@ class LanguageManager(private val plugin: Chunkmaster) {
val localizedString = langProps.getProperty(key) val localizedString = langProps.getProperty(key)
return String.format(localizedString, *replacements) 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): Reader {
return BufferedReader(InputStreamReader(stream, "UTF-8"))
}
} }

@ -22,19 +22,33 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"), Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"),
Pair("stop_after", "integer DEFAULT -1") 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<Pair<String, Pair<String, String>>>() private val needUpdate = HashSet<Pair<String, Pair<String, String>>>()
private val needCreation = HashSet<String>() private val needCreation = HashSet<String>()
private var connection: Connection? = null
private var activeTasks = 0
/** /**
* Returns the connection to the database * Returns the connection to the database
*/ */
fun getConnection(): Connection? { fun getConnection(): Connection? {
if (this.connection != null) {
return this.connection
}
try { try {
Class.forName("org.sqlite.JDBC") 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")}") "${chunkmaster.config.getString("database.filename")}")
return this.connection
} catch (e: Exception) { } catch (e: Exception) {
chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR")) chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR"))
chunkmaster.logger.severe(e.message) chunkmaster.logger.severe(e.message)
@ -79,6 +93,7 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
*/ */
fun executeStatement(sql: String, values: HashMap<Int, Any>, callback: ((ResultSet) -> Unit)?) { fun executeStatement(sql: String, values: HashMap<Int, Any>, callback: ((ResultSet) -> Unit)?) {
val connection = getConnection() val connection = getConnection()
activeTasks++
if (connection != null) { if (connection != null) {
try { try {
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
@ -92,10 +107,14 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
} }
statement.close() statement.close()
} catch (e: Exception) { } 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)) chunkmaster.logger.info(ExceptionUtils.getStackTrace(e))
} finally { } finally {
connection.close() activeTasks--
if (activeTasks == 0) {
connection.close()
this.connection = null
}
} }
} else { } else {
chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("NO_DATABASE_CONNECTION")) chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("NO_DATABASE_CONNECTION"))

@ -2,6 +2,7 @@ package net.trivernis.chunkmaster.lib.generation
import io.papermc.lib.PaperLib import io.papermc.lib.PaperLib
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.Chunk
import org.bukkit.Server import org.bukkit.Server
import org.bukkit.World import org.bukkit.World
@ -9,8 +10,18 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
val tasks: HashSet<RunningTaskEntry> = HashSet() val tasks: HashSet<RunningTaskEntry> = HashSet()
val pausedTasks: HashSet<PausedTaskEntry> = HashSet() val pausedTasks: HashSet<PausedTaskEntry> = HashSet()
val worldCenters: HashMap<String, Pair<Int, Int>> = HashMap()
val allTasks: HashSet<TaskEntry> val allTasks: HashSet<TaskEntry>
get() { get() {
if (this.tasks.isEmpty() && this.pausedTasks.isEmpty()) {
if (this.worldCenters.isEmpty()) {
this.loadWorldCenters()
}
this.startAll()
if (!server.onlinePlayers.isEmpty()) {
this.pauseAll()
}
}
val all = HashSet<TaskEntry>() val all = HashSet<TaskEntry>()
all.addAll(pausedTasks) all.addAll(pausedTasks)
all.addAll(tasks) all.addAll(tasks)
@ -25,7 +36,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
fun addTask(world: World, stopAfter: Int = -1): Int { fun addTask(world: World, stopAfter: Int = -1): Int {
val foundTask = allTasks.find { it.generationTask.world == world } val foundTask = allTasks.find { it.generationTask.world == world }
if (foundTask == null) { 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) val generationTask = createGenerationTask(world, centerChunk, centerChunk, stopAfter)
chunkmaster.sqliteManager.executeStatement( chunkmaster.sqliteManager.executeStatement(
@ -83,13 +99,14 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
center: ChunkCoordinates, center: ChunkCoordinates,
last: ChunkCoordinates, last: ChunkCoordinates,
id: Int, id: Int,
stopAfter: Int = -1 stopAfter: Int = -1,
delay: Long = 200L
) { ) {
if (!paused) { if (!paused) {
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_FOR_WORLD", world.name)) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_FOR_WORLD", world.name))
val generationTask = createGenerationTask(world, center, last, stopAfter) val generationTask = createGenerationTask(world, center, last, stopAfter)
val task = server.scheduler.runTaskTimer( val task = server.scheduler.runTaskTimer(
chunkmaster, generationTask, 200, // 10 sec delay chunkmaster, generationTask, delay, // 10 sec delay
chunkmaster.config.getLong("generation.period") chunkmaster.config.getLong("generation.period")
) )
tasks.add(RunningTaskEntry(id, task, generationTask)) tasks.add(RunningTaskEntry(id, task, generationTask))
@ -139,8 +156,10 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
saveProgress() // save progress every 30 seconds saveProgress() // save progress every 30 seconds
}, 600, 600) }, 600, 600)
server.scheduler.runTaskLater(chunkmaster, Runnable { server.scheduler.runTaskLater(chunkmaster, Runnable {
if (server.onlinePlayers.isEmpty()) { this.loadWorldCenters()
startAll() // run startAll after 10 seconds if empty this.startAll()
if (!server.onlinePlayers.isEmpty()) {
this.pauseAll()
} }
}, 600) }, 600)
} }
@ -149,11 +168,14 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
* Stops all generation tasks * Stops all generation tasks
*/ */
fun stopAll() { fun stopAll() {
saveProgress()
val removalSet = HashSet<RunningTaskEntry>() val removalSet = HashSet<RunningTaskEntry>()
for (task in tasks) { 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.task.cancel()
task.generationTask.cancel()
if (task.task.isCancelled) { if (task.task.isCancelled) {
removalSet.add(task) removalSet.add(task)
} }
@ -167,7 +189,9 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
*/ */
fun startAll() { fun startAll() {
chunkmaster.sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) { res -> chunkmaster.sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) { res ->
var count = 0
while (res.next()) { while (res.next()) {
count++
try { try {
val id = res.getInt("id") val id = res.getInt("id")
val world = server.getWorld(res.getString("world")) val world = server.getWorld(res.getString("world"))
@ -175,7 +199,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z")) val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z"))
val stopAfter = res.getInt("stop_after") val stopAfter = res.getInt("stop_after")
if (this.tasks.find { it.id == id } == null) { 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) { } catch (error: NullPointerException) {
chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TASK_LOAD_FAILED", res.getInt("id"))) chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TASK_LOAD_FAILED", res.getInt("id")))
@ -208,6 +232,51 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
startAll() 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<Int, Int>) {
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 * Saves the task progress
*/ */
@ -221,7 +290,6 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
)}%)" else "" )}%)" else ""
val eta = if (genTask.stopAfter > 0 && speed > 0) { val eta = if (genTask.stopAfter > 0 && speed > 0) {
val etaSeconds = (genTask.stopAfter - genTask.count).toDouble()/speed val etaSeconds = (genTask.stopAfter - genTask.count).toDouble()/speed
chunkmaster.logger.info(""+etaSeconds)
val hours: Int = (etaSeconds/3600).toInt() val hours: Int = (etaSeconds/3600).toInt()
val minutes: Int = ((etaSeconds % 3600) / 60).toInt() val minutes: Int = ((etaSeconds % 3600) / 60).toInt()
val seconds: Int = (etaSeconds % 60).toInt() val seconds: Int = (etaSeconds % 60).toInt()
@ -239,20 +307,27 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
speed, speed,
genTask.lastChunk.x, genTask.lastChunk.x,
genTask.lastChunk.z)) genTask.lastChunk.z))
chunkmaster.sqliteManager.executeStatement( saveProgressToDatabase(genTask.lastChunkCoords, task.id)
"""
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
)
} catch (error: Exception) { } catch (error: Exception) {
chunkmaster.logger.warning(chunkmaster.langManager.getLocalized("TASK_SAVE_FAILED", error.toString())) 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 * Creates a new generation task. This method is used to create a task depending
* on the server type (Paper/Spigot). * on the server type (Paper/Spigot).

@ -3,30 +3,39 @@ package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Spiral import net.trivernis.chunkmaster.lib.Spiral
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.Location
import org.bukkit.World import org.bukkit.World
import kotlin.math.*
/** /**
* Interface for generation tasks. * 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 { Runnable {
abstract val stopAfter: Int abstract val stopAfter: Int
abstract val world: World abstract val world: World
abstract val count: Int abstract val count: Int
abstract val endReached: Boolean abstract var endReached: Boolean
protected val spiral: Spiral = protected val spiral: Spiral =
Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z)) Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z))
protected val loadedChunks: HashSet<Chunk> = HashSet() protected val loadedChunks: HashSet<Chunk> = 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 chunkSkips = plugin.config.getInt("generation.chunk-skips-per-step")
protected val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold") protected val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold")
protected val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks") protected val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks")
protected val chunksPerStep = plugin.config.getInt("generation.chunks-per-step") 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 protected var endReachedCallback: ((GenerationTask) -> Unit)? = null
private set 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 override fun run()
abstract fun cancel() abstract fun cancel()
@ -55,6 +64,77 @@ abstract class GenerationTask(plugin: Chunkmaster, centerChunk: ChunkCoordinates
|| (stopAfter in 1..count) || (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<ChunkCoordinates, ChunkCoordinates> {
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 * Registers end reached callback
*/ */

@ -18,7 +18,10 @@ class GenerationTaskPaper(
override var count = 0 override var count = 0
private set private set
override var endReached: Boolean = false override var endReached: Boolean = false
private set
init {
updateDynmapMarker()
}
/** /**
* Runs the generation task. Every Iteration the next chunk will be generated if * Runs the generation task. Every Iteration the next chunk will be generated if
@ -28,16 +31,10 @@ class GenerationTaskPaper(
override fun run() { override fun run() {
if (plugin.mspt < msptThreshold) { // pause when tps < 2 if (plugin.mspt < msptThreshold) { // pause when tps < 2
if (loadedChunks.size > maxLoadedChunks) { if (loadedChunks.size > maxLoadedChunks) {
for (chunk in loadedChunks) { unloadLoadedChunks()
if (chunk.isLoaded) {
chunk.unload(true)
}
}
loadedChunks.clear()
} else if (pendingChunks.size < maxPendingChunks) { // if more than 10 chunks are pending, wait. } else if (pendingChunks.size < maxPendingChunks) { // if more than 10 chunks are pending, wait.
if (borderReached()) { if (borderReached()) {
endReached = true setEndReached()
endReachedCallback?.invoke(this)
return return
} }
@ -73,13 +70,14 @@ class GenerationTaskPaper(
* This unloads all chunks that were generated but not unloaded yet. * This unloads all chunks that were generated but not unloaded yet.
*/ */
override fun cancel() { override fun cancel() {
updateDynmapMarker(true)
unloadAllChunks() unloadAllChunks()
} }
/** /**
* Cancels all pending chunks and unloads all loaded chunks. * Cancels all pending chunks and unloads all loaded chunks.
*/ */
fun unloadAllChunks() { private fun unloadAllChunks() {
for (pendingChunk in pendingChunks) { for (pendingChunk in pendingChunks) {
if (pendingChunk.isDone) { if (pendingChunk.isDone) {
loadedChunks.add(pendingChunk.get()) loadedChunks.add(pendingChunk.get())

@ -2,6 +2,7 @@ package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.World import org.bukkit.World
import java.lang.Exception
class GenerationTaskSpigot( class GenerationTaskSpigot(
private val plugin: Chunkmaster, override val world: World, private val plugin: Chunkmaster, override val world: World,
@ -13,7 +14,10 @@ class GenerationTaskSpigot(
override var count = 0 override var count = 0
private set private set
override var endReached: Boolean = false override var endReached: Boolean = false
private set
init {
updateDynmapMarker()
}
/** /**
* Runs the generation task. Every Iteration the next chunk will be generated if * Runs the generation task. Every Iteration the next chunk will be generated if
@ -23,16 +27,10 @@ class GenerationTaskSpigot(
override fun run() { override fun run() {
if (plugin.mspt < msptThreshold) { // pause when tps < 2 if (plugin.mspt < msptThreshold) { // pause when tps < 2
if (loadedChunks.size > maxLoadedChunks) { if (loadedChunks.size > maxLoadedChunks) {
for (chunk in loadedChunks) { unloadLoadedChunks()
if (chunk.isLoaded) {
chunk.unload(true)
}
}
loadedChunks.clear()
} else { } else {
if (borderReached()) { if (borderReached()) {
endReached = true setEndReached()
endReachedCallback?.invoke(this)
return return
} }
@ -62,8 +60,13 @@ class GenerationTaskSpigot(
override fun cancel() { override fun cancel() {
for (chunk in loadedChunks) { for (chunk in loadedChunks) {
if (chunk.isLoaded) { if (chunk.isLoaded) {
chunk.unload(true) try {
chunk.unload(true)
} catch (e: Exception) {
plugin.logger.severe(e.toString())
}
} }
} }
updateDynmapMarker(true)
} }
} }

@ -1,48 +1,56 @@
RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'... RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'...
TASK_FINISHED = Task #%d finished after %d chunks. TASK_FINISHED = Task #%d finished after %d chunks.
TASK_CANCELED = Canceled task #%s. 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_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... 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_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_NAME_REQUIRED = §cYou need to provide a world name!
WORLD_NOT_FOUND = \u00A7cWorld \u00A72%s \u00A7cnot found! WORLD_NOT_FOUND = §cWorld §2%s §cnot found!
TASK_ALREADY_EXISTS = \u00A7cA task for '%s' already exists! TASK_ALREADY_EXISTS = §cA task for '%s' already exists!
TASK_CREATION_SUCCESS = \u00A79Generation Task for world \u00A72%s \u00A79 until \u00A72%s \u00A79successfully created! TASK_CREATION_SUCCESS = §9Generation Task for world §2%s §9 until §2%s §9successfully created!
TASK_ID_REQUIRED = \u00A7cYou need to provide a task id! TASK_ID_REQUIRED = §cYou need to provide a task id!
PAUSED_TASKS_HEADER = Currently Paused Generation Tasks 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 RUNNING_TASKS_HEADER = Currently Running Generation Tasks
NO_GENERATION_TASKS = There are no generation tasks. NO_GENERATION_TASKS = There are no generation tasks.
PAUSE_SUCCESS = \u00A79Paused all generation tasks. PAUSE_SUCCESS = §9Paused all generation tasks.
ALREADY_PAUSED = \u00A7cThe generation process is already paused! ALREADY_PAUSED = §cThe generation process is already paused!
RESUME_SUCCESS = \u00A79Resumed all generation Tasks. RESUME_SUCCESS = §9Resumed all generation Tasks.
NOT_PAUSED = \u00A7cThe generation process is not paused! NOT_PAUSED = §cThe generation process is not paused!
CONFIG_RELOADING = Reloading the config file... 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 TELEPORTED = §9You have been teleported to chunk §2%s, %s
TP_ONLY_PLAYER = \u00A7cThis command can only be executed by a player! TP_ONLY_PLAYER = §cThis command can only be executed by a player!
NO_PERMISSION = \u00A7cYou do not have the permission for this command! NO_PERMISSION = §cYou do not have the permission for this command!
SUBCOMMAND_NOT_FOUND = \u00A7cSubcommand \u00A72%s \u00A7cnot found! SUBCOMMAND_NOT_FOUND = §cSubcommand §2%s §cnot found!
STOPPING_ALL_TASKS = Stopping all generation tasks... 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 = Initializing database...
DB_INIT_FINISHED = Database fully initialized. DB_INIT_FINISHED = Database fully initialized.
DB_INIT_EROR = Failed to init database: %s. DB_INIT_EROR = Failed to init database: %s.
DATABASE_CONNECTION_ERROR = \u00A7cCould not get the database connection! DATABASE_CONNECTION_ERROR = §cCould not get the database connection!
SQL_ERROR = \u00A7cAn eror occured on sql %s! SQL_ERROR = §cAn eror occured on sql %s!
NO_DATABASE_CONNECTION = \u00A7cCould not execute sql: No database connection. NO_DATABASE_CONNECTION = §cCould not execute sql: No database connection.
CREATE_TABLE_DEFINITION = Created table %s with definition %s. 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_DEFINITION = Updated table %s with sql %s.
UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. 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.
PLUGIN_DETECTED = Detected %s version %s

@ -1,48 +1,56 @@
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_FINISHED = Aufgabe #%d wurde nach %d chunks beendet.
TASK_CANCELED = Aufgabe #%s wurde abgebrochen. 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_LOAD_SUCCESS = %d gespeicherte Aufgaben wurden geladen.
TASK_NOT_FOUND = \u00A7cAufgabe %s konnte nicht gefunden werden! TASK_NOT_FOUND = §cAufgabe %s konnte nicht gefunden werden!
CREATE_DELAYED_LOAD = Erstelle einen Bukkit-Task zum verz\u00f6gerten Laden von Aufgaben... CREATE_DELAYED_LOAD = Erstelle einen Bukkit-Task zum verzögerten 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_PERIODIC_REPORT = Aufgabe #%d für 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_SAVE_FAILED = §cFehler beim Speichern der Aufgaben: %s
WORLD_NAME_REQUIRED = \u00A7cDu musst einen Weltennamen angeben! WORLD_NAME_REQUIRED = §cDu musst einen Weltennamen angeben!
WORLD_NOT_FOUND = \u00A7c Die Welt \u00A72%s \u00A7cwurde nicht gefunden! WORLD_NOT_FOUND = §c Die Welt §2%s §cwurde nicht gefunden!
TASK_ALREADY_EXISTS = \u00A7cEs existiert bereits eine Aufgabe f\u00fcr \u00A72%s\u00A7c! TASK_ALREADY_EXISTS = §cEs existiert bereits eine Aufgabe für §2%s§c!
TASK_CREATION_SUCCESS = \u00A79Generierungs-Aufgabe \u00A72%s \u00A79 bis \u00A72%s \u00A79wurde erfolgreich erstellt! TASK_CREATION_SUCCESS = §9Generierungs-Aufgabe §2%s §9 bis §2%s §9wurde erfolgreich erstellt!
TASK_ID_REQUIRED = \u00A7cDu musst eine Aufgaben-Id angeben! 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. NO_GENERATION_TASKS = Es gibt keine Aufgaben.
PAUSE_SUCCESS = \u00A79Alle Aufgaben wurden pausiert. PAUSE_SUCCESS = §9Alle Aufgaben wurden pausiert.
ALREADY_PAUSED = \u00A7cDas Generieren ist bereits pausiert. ALREADY_PAUSED = §cDas Generieren ist bereits pausiert.
RESUME_SUCCESS = \u00A79Alle Aufgaben wurden fortgesetzt. RESUME_SUCCESS = §9Alle Aufgaben wurden fortgesetzt.
NOT_PAUSED = \u00A7cEs gibt keine pausierten Aufgaben! NOT_PAUSED = §cEs gibt keine pausierten Aufgaben!
CONFIG_RELOADING = Die Konfigurationsdatei wird neu eingelesen... 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 TELEPORTED = §9Du wurdest zum Chunk §2%s, %s §9teleportiert
TP_ONLY_PLAYER = \u00A7cDieser Befehl kann nur von einem Spieler ausgef\u00fchrt werden. TP_ONLY_PLAYER = §cDieser Befehl kann nur von einem Spieler ausgeführt werden.
NO_PERMISSION = \u00A7cDu hast nicht die Rechte für diesen Befehl! NO_PERMISSION = §cDu hast nicht die Rechte für diesen Befehl!
SUBCOMMAND_NOT_FOUND = \u00A7cUnteraktion \u00A72%s \u00A7cwurde nicht gefunden! SUBCOMMAND_NOT_FOUND = §cUnteraktion §2%s §cwurde nicht gefunden!
STOPPING_ALL_TASKS = Stoppt alle Aufgaben... STOPPING_ALL_TASKS = Stoppt alle Aufgaben...
SAVING_TASK_PROGRESS = Speichert den Fortschritt der Aufgabe #%s in der Datenbank...
DB_INIT = Initialisiere Datenbank... DB_INIT = Initialisiere Datenbank...
DB_INIT_FINISHED = Die Datenbank wurde initialisiert. DB_INIT_FINISHED = Die Datenbank wurde initialisiert.
DB_INIT_EROR = Fehler beim Initalisieren der Datenbank: %s. DB_INIT_EROR = Fehler beim Initalisieren der Datenbank: %s.
DATABASE_CONNECTION_ERROR = \u00A7cDie Datenbankverbindung konnte nicht erzeugt werden. DATABASE_CONNECTION_ERROR = §cDie Datenbankverbindung konnte nicht erzeugt werden.
SQL_ERROR = \u00A7cEin Fehler trat mit sql %s auf! SQL_ERROR = §cEin Fehler trat mit sql %s auf!
NO_DATABASE_CONNECTION = \u00A7cSql konnte nicht ausgef\u00fchrt werden: Keine Datenbankverbindung. NO_DATABASE_CONNECTION = §cSql konnte nicht ausgeführt werden: Keine Datenbankverbindung.
CREATE_TABLE_DEFINITION = Tabelle %s mit Definition %s wurde erstellt. 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_DEFINITION = Tabelle %s wurde mit sql %s geupdated.
UPDATE_TABLE_FAILED = Fehler beim Updaten der Tabelle %s mit sql %s. 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.
PLUGIN_DETECTED = Plugin %s in der Version %s gefunden!

@ -1,48 +1,56 @@
RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'... RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'...
TASK_FINISHED = Task #%d finished after %d chunks. TASK_FINISHED = Task #%d finished after %d chunks.
TASK_CANCELED = Canceled task #%s. 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_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... 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_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_NAME_REQUIRED = §cYou need to provide a world name!
WORLD_NOT_FOUND = \u00A7cWorld \u00A72%s \u00A7cnot found! WORLD_NOT_FOUND = §cWorld §2%s §cnot found!
TASK_ALREADY_EXISTS = \u00A7cA task for '%s' already exists! TASK_ALREADY_EXISTS = §cA task for '%s' already exists!
TASK_CREATION_SUCCESS = \u00A79Generation Task for world \u00A72%s \u00A79 until \u00A72%s \u00A79successfully created! TASK_CREATION_SUCCESS = §9Generation Task for world §2%s §9 until §2%s §9successfully created!
TASK_ID_REQUIRED = \u00A7cYou need to provide a task id! TASK_ID_REQUIRED = §cYou need to provide a task id!
PAUSED_TASKS_HEADER = Currently Paused Generation Tasks 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 RUNNING_TASKS_HEADER = Currently Running Generation Tasks
NO_GENERATION_TASKS = There are no generation tasks. NO_GENERATION_TASKS = There are no generation tasks.
PAUSE_SUCCESS = \u00A79Paused all generation tasks. PAUSE_SUCCESS = §9Paused all generation tasks.
ALREADY_PAUSED = \u00A7cThe generation process is already paused! ALREADY_PAUSED = §cThe generation process is already paused!
RESUME_SUCCESS = \u00A79Resumed all generation Tasks. RESUME_SUCCESS = §9Resumed all generation Tasks.
NOT_PAUSED = \u00A7cThe generation process is not paused! NOT_PAUSED = §cThe generation process is not paused!
CONFIG_RELOADING = Reloading the config file... 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 TELEPORTED = §9You have been teleported to chunk §2%s, %s
TP_ONLY_PLAYER = \u00A7cThis command can only be executed by a player! TP_ONLY_PLAYER = §cThis command can only be executed by a player!
NO_PERMISSION = \u00A7cYou do not have the permission for this command! NO_PERMISSION = §cYou do not have the permission for this command!
SUBCOMMAND_NOT_FOUND = \u00A7cSubcommand \u00A72%s \u00A7cnot found! SUBCOMMAND_NOT_FOUND = §cSubcommand §2%s §cnot found!
STOPPING_ALL_TASKS = Stopping all generation tasks... 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 = Initializing database...
DB_INIT_FINISHED = Database fully initialized. DB_INIT_FINISHED = Database fully initialized.
DB_INIT_EROR = Failed to init database: %s. DB_INIT_EROR = Failed to init database: %s.
DATABASE_CONNECTION_ERROR = \u00A7cCould not get the database connection! DATABASE_CONNECTION_ERROR = §cCould not get the database connection!
SQL_ERROR = \u00A7cAn eror occured on sql %s! SQL_ERROR = §cAn eror occured on sql %s!
NO_DATABASE_CONNECTION = \u00A7cCould not execute sql: No database connection. NO_DATABASE_CONNECTION = §cCould not execute sql: No database connection.
CREATE_TABLE_DEFINITION = Created table %s with definition %s. 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_DEFINITION = Updated table %s with sql %s.
UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. 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.
PLUGIN_DETECTED = Detected %s version %s

@ -1,10 +1,13 @@
main: net.trivernis.chunkmaster.Chunkmaster main: net.trivernis.chunkmaster.Chunkmaster
name: Chunkmaster name: Chunkmaster
version: '0.14-beta' version: '0.15-beta'
description: Chunk commands plugin. description: Chunk commands plugin.
author: Trivernis author: Trivernis
website: trivernis.net website: trivernis.net
api-version: '1.14' api-version: '1.14'
database: true
softdepend:
- dynmap
commands: commands:
chunkmaster: chunkmaster:
description: Main command description: Main command
@ -17,6 +20,8 @@ commands:
/<command> resume - resumes all generation tasks /<command> resume - resumes all generation tasks
/<command> reload - reloads the configuration and restarts all tasks /<command> reload - reloads the configuration and restarts all tasks
/<command> tpchunk <chunkX> <chunkZ> - teleports you to the chunk with the given chunk coordinates /<command> tpchunk <chunkX> <chunkZ> - teleports you to the chunk with the given chunk coordinates
/<command> setCenter [<world>] <chunkX> <chunkZ> - sets the center chunk of the world
/<command> getCenter [<world>] - returns the center chunk of the world
aliases: aliases:
- chm - chm
- chunkm - chunkm
@ -43,6 +48,12 @@ permissions:
chunkmaster.tpchunk: chunkmaster.tpchunk:
description: Allows the tpchunk subcommand. description: Allows the tpchunk subcommand.
default: op default: op
chunkmaster.setcenter:
description: Allows the setCenter subcommand.
default: op
chunkmaster.getcenter:
description: Allows the getCenter subcommand.
default: op
chunkmaster.chunkmaster: chunkmaster.chunkmaster:
description: Allows Chunkmaster commands. description: Allows Chunkmaster commands.
default: op default: op

Loading…
Cancel
Save