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