diff --git a/README.md b/README.md index 9d5e745..8ab033d 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,19 @@ This plugin can be used to pre-generate the region of a world around the spawn c The generation automatically pauses when a player joins the server (assuming the server was empty before) and resumes when the server is empty again. The generation also auto-resumes after a server restart. The plugin tracks the ticks per second and pauses the generation when the tps -is lower than 2. +is lower than 2 (configurable). ## Commands All features can be accessed with the command `/chunkmaster` or the aliases `/chm`, `chunkm`, `cmaster`. -- `/chunkmaster generate [world] [chunk count]` Starts the generation until the specified chunk count or the world border is reached. +- `/chunkmaster generate [world] [chunk count] [unit]` Starts the generation until the specified chunk count or the world border is reached. - `/chunkmaster list` Lists all running generation tasks - `/chunkmaster cancel ` Cancels the generation task with the specified id (if it is running). - `/chunkmaster pause` Pauses all generation tasks until the resume command is executed. - `/chunkmaster resume` Resumes all paused generation tasks. - `/chunkmaster reload` Reloads the configuration file. +- `/chunkmaster tpchunk ` Teleports you to the specified chunk coordinates. ## Config @@ -39,13 +40,18 @@ generation: # The value should be a positive integer. period: 2 + # The max amount of chunks that should be generated per step. + # Higher values mean higher generation speed but higher performance impact. + # The value should be a positive integer. + chunks-per-step: 4 + # Paper Only # The number of already generated chunks that will be skipped for each step. # Notice that these still have a performance impact because the server needs to check # if the chunk is generated. # Higher values mean faster generation but greater performance impact. # The value should be a positive integer. - chunks-skips-per-step: 5 + chunk-skips-per-step: 100 # The maximum milliseconds per tick the server is allowed to have # during the cunk generation process. diff --git a/build.gradle b/build.gradle index 581da97..3e250af 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ idea { } group "net.trivernis" -version "0.11-beta" +version "0.12-beta" sourceCompatibility = 1.8 diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index 6d5ab8b..444b06b 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -32,8 +32,8 @@ class Chunkmaster: JavaPlugin() { generationManager = GenerationManager(this, server) generationManager.init() - getCommand("chunkmaster")?.setExecutor(CommandChunkmaster(this, server)) getCommand("chunkmaster")?.aliases = mutableListOf("chm", "chunkm", "cmaster") + getCommand("chunkmaster")?.setExecutor(CommandChunkmaster(this, server)) server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this) @@ -60,7 +60,8 @@ class Chunkmaster: JavaPlugin() { private fun configure() { dataFolder.mkdir() config.addDefault("generation.period", 2L) - config.addDefault("generation.chunks-skips-per-step", 10) + config.addDefault("generation.chunks-per-step", 2) + config.addDefault("generation.chunk-skips-per-step", 100) config.addDefault("generation.mspt-pause-threshold", 500L) config.addDefault("generation.pause-on-join", true) config.addDefault("generation.max-pending-chunks", 10) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt b/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt index 60328f5..f139d22 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt @@ -1,10 +1,12 @@ package net.trivernis.chunkmaster +import net.trivernis.chunkmaster.lib.generation.GenerationTaskPaper import org.bukkit.Server import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerQuitEvent +import org.bukkit.event.world.WorldSaveEvent class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server) : Listener { @@ -21,8 +23,10 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server server.onlinePlayers.isEmpty() ) { if (!playerPaused) { + if (chunkmaster.generationManager.pausedTasks.isNotEmpty()) { + chunkmaster.logger.info("Server is empty. Resuming chunk generation tasks.") + } chunkmaster.generationManager.resumeAll() - chunkmaster.logger.info("Server is empty. Resuming chunk generation tasks.") } else if (chunkmaster.generationManager.paused){ chunkmaster.logger.info("Generation was manually paused. Not resuming automatically.") playerPaused = chunkmaster.generationManager.paused @@ -40,9 +44,24 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server fun onPlayerJoin(event: PlayerJoinEvent) { if (pauseOnJoin) { if (server.onlinePlayers.size == 1 || server.onlinePlayers.isEmpty()) { + if (chunkmaster.generationManager.tasks.isNotEmpty()) { + chunkmaster.logger.info("Pausing generation tasks because of player join.") + } playerPaused = chunkmaster.generationManager.paused chunkmaster.generationManager.pauseAll() - chunkmaster.logger.info("Pausing generation tasks because of player join.") + } + } + } + + /** + * Unload all chunks before a save. + */ + @EventHandler + fun onWorldSave(event: WorldSaveEvent) { + val task = chunkmaster.generationManager.tasks.find { it.generationTask.world == event.world } + if (task != null) { + if (task.generationTask is GenerationTaskPaper) { + task.generationTask.unloadAllChunks() } } } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGenerate.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGenerate.kt index b9a5431..7b25762 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGenerate.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGenerate.kt @@ -7,6 +7,7 @@ import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.entity.Player +import kotlin.math.pow class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { override val name = "generate" @@ -23,9 +24,19 @@ class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { if (args.size == 1) { return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } .map {it.name}.toMutableList() + } else if (args.size == 2) { + if (args[0].toIntOrNull() != null) { + return units.filter {it.indexOf(args[1]) == 0}.toMutableList() + } + } else if (args.size > 2) { + if (args[1].toIntOrNull() != null) { + return units.filter {it.indexOf(args[2]) == 0}.toMutableList() + } } return emptyList().toMutableList() } + val units = listOf("blockradius", "radius", "diameter") + /** * Creates a new generation task for the world and chunk count. @@ -37,11 +48,21 @@ class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { if (args.isNotEmpty()) { if (args[0].toIntOrNull() != null) { stopAfter = args[0].toInt() + worldName = sender.world.name } else { worldName = args[0] } - if (args.size > 1 && args[1].toIntOrNull() != null) { - stopAfter = args[1].toInt() + if (args.size > 1) { + if (args[1].toIntOrNull() != null) { + stopAfter = args[1].toInt() + } else if (args[1] in units && args[0].toIntOrNull() != null) { + stopAfter = getStopAfter(stopAfter, args[1]) + } else { + worldName = args[1] + } + } + if (args.size > 2 && args[2] in units && args[1].toIntOrNull() != null) { + stopAfter = getStopAfter(stopAfter, args[2]) } } else { worldName = sender.world.name @@ -49,8 +70,13 @@ class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { } else { if (args.isNotEmpty()) { worldName = args[0] - if (args.size > 1 && args[1].toIntOrNull() != null) { - stopAfter = args[1].toInt() + if (args.size > 1) { + if (args[1].toIntOrNull() != null) { + stopAfter = args[1].toInt() + } + } + if (args.size > 2 && args[2] in units) { + stopAfter = getStopAfter(stopAfter, args[2]) } } else { sender.spigot().sendMessage( @@ -58,6 +84,34 @@ class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { return false } } + return createTask(sender, worldName, stopAfter) + } + + /** + * Returns stopAfter for a given unit + */ + private fun getStopAfter(number: Int, unit: String): Int { + if (unit in units) { + return when (unit) { + "radius" -> { + ((number * 2)+1).toDouble().pow(2.0).toInt() + } + "diameter" -> { + number.toDouble().pow(2.0).toInt() + } + "blockradius" -> { + ((number.toDouble()+1)/8).pow(2.0).toInt() + } + else -> number + } + } + return number + } + + /** + * Creates the task with the given arguments. + */ + private fun createTask(sender: CommandSender, worldName: String, stopAfter: Int): Boolean { val world = chunkmaster.server.getWorld(worldName) val allTasks = chunkmaster.generationManager.allTasks return if (world != null && (allTasks.find { it.generationTask.world == world }) == null) { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt new file mode 100644 index 0000000..419165e --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdReload.kt @@ -0,0 +1,34 @@ +package net.trivernis.chunkmaster.commands + +import net.md_5.bungee.api.ChatColor +import net.md_5.bungee.api.chat.ComponentBuilder +import net.trivernis.chunkmaster.Chunkmaster +import net.trivernis.chunkmaster.lib.Subcommand +import org.bukkit.command.Command +import org.bukkit.command.CommandSender + +class CmdReload(private val chunkmaster: Chunkmaster): Subcommand { + override val name = "reload" + + override fun onTabComplete( + sender: CommandSender, + command: Command, + alias: String, + args: List + ): MutableList { + return emptyList().toMutableList() + } + + /** + * Reload command to reload the config and restart the tasks. + */ + override fun execute(sender: CommandSender, args: List): Boolean { + sender.spigot().sendMessage(*ComponentBuilder("Reloading config and restarting tasks...") + .color(ChatColor.YELLOW).create()) + chunkmaster.generationManager.stopAll() + chunkmaster.reloadConfig() + chunkmaster.generationManager.startAll() + sender.spigot().sendMessage(*ComponentBuilder("Config reload complete!").color(ChatColor.GREEN).create()) + return true + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdTpChunk.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdTpChunk.kt new file mode 100644 index 0000000..89253bd --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdTpChunk.kt @@ -0,0 +1,52 @@ +package net.trivernis.chunkmaster.commands + +import io.papermc.lib.PaperLib +import net.md_5.bungee.api.ChatColor +import net.md_5.bungee.api.chat.ComponentBuilder +import net.trivernis.chunkmaster.lib.Subcommand +import org.bukkit.Material +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class CmdTpChunk: Subcommand { + override val name = "tpchunk" + + override fun onTabComplete( + sender: CommandSender, + command: Command, + alias: String, + args: List + ): MutableList { + return emptyList().toMutableList() + } + + /** + * Teleports the player to a save location in the chunk + */ + override fun execute(sender: CommandSender, args: List): Boolean { + if (sender is Player) { + if (args.size == 2 && args[0].toIntOrNull() != null && args[1].toIntOrNull() != null) { + val location = sender.world.getChunkAt(args[0].toInt(), args[1].toInt()).getBlock(8, 60, 8).location + + while (location.block.blockData.material != Material.AIR) { + location.y++ + } + if (PaperLib.isPaper()) { + PaperLib.teleportAsync(sender, location) + } else { + sender.teleport(location) + } + sender.spigot().sendMessage(*ComponentBuilder("You have been teleportet to chunk") + .color(ChatColor.YELLOW).append("${args[0]}, ${args[1]}").color(ChatColor.BLUE).create()) + return true + } else { + return false + } + } else { + sender.spigot().sendMessage(*ComponentBuilder("This command can only be executed by a player!") + .color(ChatColor.RED).create()) + return false + } + } +} \ 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 ef313a8..50bd98e 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt @@ -10,7 +10,7 @@ import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.command.TabCompleter -class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val server: Server): CommandExecutor, +class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val server: Server) : CommandExecutor, TabCompleter { private val commands = HashMap() @@ -25,7 +25,7 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve MutableList { if (args.size == 1) { return commands.keys.filter { it.indexOf(args[0]) == 0 }.toMutableList() - } else if (args.isNotEmpty()){ + } else if (args.isNotEmpty()) { if (commands.containsKey(args[0])) { val commandEntry = commands[args[0]] @@ -40,25 +40,21 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve */ override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { if (args.isNotEmpty()) { - when (args[0]) { - "reload" -> { - chunkmaster.reloadConfig() - sender.sendMessage("Configuration file reloaded.") - } - else -> { - if (sender.hasPermission("chunkmaster.${args[0]}")) { - return if (commands.containsKey(args[0])) { - commands[args[0]]!!.execute(sender, args.slice(1 until args.size)) - } else { - sender.spigot().sendMessage(*ComponentBuilder("Subcommand ").color(ChatColor.RED) - .append(args[0]).color(ChatColor.GREEN).append(" not found").color(ChatColor.RED).create()) - false - } - } else { - sender.spigot().sendMessage(*ComponentBuilder("You do not have permission!") - .color(ChatColor.RED).create()) - } + if (sender.hasPermission("chunkmaster.${args[0]}")) { + return if (commands.containsKey(args[0])) { + commands[args[0]]!!.execute(sender, args.slice(1 until args.size)) + } else { + sender.spigot().sendMessage( + *ComponentBuilder("Subcommand ").color(ChatColor.RED) + .append(args[0]).color(ChatColor.GREEN).append(" not found").color(ChatColor.RED).create() + ) + false } + } else { + sender.spigot().sendMessage( + *ComponentBuilder("You do not have permission!") + .color(ChatColor.RED).create() + ) } return true } else { @@ -66,7 +62,10 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve } } - fun registerCommands() { + /** + * Registers all subcommands. + */ + private fun registerCommands() { val cmdGenerate = CmdGenerate(chunkmaster) commands[cmdGenerate.name] = cmdGenerate @@ -81,5 +80,11 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve val cmdList = CmdList(chunkmaster) commands[cmdList.name] = cmdList + + val cmdReload = CmdReload(chunkmaster) + commands[cmdReload.name] = cmdReload + + val cmdTpChunk = CmdTpChunk() + commands[cmdTpChunk.name] = cmdTpChunk } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqlUpdateManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/SqlUpdateManager.kt index 5da91d6..ad18e03 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqlUpdateManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/SqlUpdateManager.kt @@ -15,8 +15,7 @@ class SqlUpdateManager(private val connnection: Connection, private val chunkmas Pair("last_x", "integer NOT NULL DEFAULT 0"), Pair("last_z", "integer NOT NULL DEFAULT 0"), Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"), - Pair("stop_after", "integer DEFAULT -1"), - Pair("autostart", "integer DEFAULT 1") + Pair("stop_after", "integer DEFAULT -1") ) ) ) 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 a7d01d4..48d61ce 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -28,13 +28,15 @@ 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 = world.getChunkAt(world.spawnLocation) + val centerChunk = ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) val generationTask = createGenerationTask(world, centerChunk, centerChunk, stopAfter) - val insertStatement = chunkmaster.sqliteConnection.prepareStatement(""" + val insertStatement = chunkmaster.sqliteConnection.prepareStatement( + """ INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, stop_after) values (?, ?, ?, ?, ?, ?) - """) + """ + ) insertStatement.setInt(1, centerChunk.x) insertStatement.setInt(2, centerChunk.z) insertStatement.setInt(3, centerChunk.x) @@ -43,9 +45,11 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server insertStatement.setInt(6, stopAfter) insertStatement.execute() - val getIdStatement = chunkmaster.sqliteConnection.prepareStatement(""" + val getIdStatement = chunkmaster.sqliteConnection.prepareStatement( + """ SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1 - """.trimIndent()) + """.trimIndent() + ) getIdStatement.execute() val result = getIdStatement.resultSet result.next() @@ -55,13 +59,15 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server getIdStatement.close() generationTask.onEndReached { - server.consoleSender.sendMessage("Task #${id} finished after ${generationTask.count} chunks.") + chunkmaster.logger.info("Task #${id} finished after ${generationTask.count} chunks.") removeTask(id) } if (!paused) { - val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, - chunkmaster.config.getLong("generation.period")) + val task = server.scheduler.runTaskTimer( + chunkmaster, generationTask, 200, // 10 sec delay + chunkmaster.config.getLong("generation.period") + ) tasks.add(RunningTaskEntry(id, task, generationTask)) } else { pausedTasks.add(PausedTaskEntry(id, generationTask)) @@ -76,15 +82,23 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server /** * Resumes a generation task */ - private fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int, stopAfter: Int = -1) { + private fun resumeTask( + world: World, + center: ChunkCoordinates, + last: ChunkCoordinates, + id: Int, + stopAfter: Int = -1 + ) { if (!paused) { chunkmaster.logger.info("Resuming chunk generation task for world \"${world.name}\"") val generationTask = createGenerationTask(world, center, last, stopAfter) - val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, - chunkmaster.config.getLong("generation.period")) + val task = server.scheduler.runTaskTimer( + chunkmaster, generationTask, 200, // 10 sec delay + chunkmaster.config.getLong("generation.period") + ) tasks.add(RunningTaskEntry(id, task, generationTask)) generationTask.onEndReached { - server.consoleSender.sendMessage("Task #${id} finished after ${generationTask.count} chunks.") + chunkmaster.logger.info("Task #${id} finished after ${generationTask.count} chunks.") removeTask(id) } } @@ -95,15 +109,17 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server */ fun removeTask(id: Int): Boolean { val taskEntry: TaskEntry? = if (this.paused) { - this.pausedTasks.find {it.id == id} + this.pausedTasks.find { it.id == id } } else { - this.tasks.find {it.id == id} + this.tasks.find { it.id == id } } if (taskEntry != null) { taskEntry.cancel() - val deleteTask = chunkmaster.sqliteConnection.prepareStatement(""" + val deleteTask = chunkmaster.sqliteConnection.prepareStatement( + """ DELETE FROM generation_tasks WHERE id = ?; - """.trimIndent()) + """.trimIndent() + ) deleteTask.setInt(1, taskEntry.id) deleteTask.execute() deleteTask.close() @@ -133,7 +149,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server if (server.onlinePlayers.isEmpty()) { startAll() // run startAll after 10 seconds if empty } - }, 200) + }, 600) } /** @@ -157,28 +173,27 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server * Starts all generation tasks. */ fun startAll() { - chunkmaster.logger.info("Loading saved chunk generation tasks...") val savedTasksStatement = chunkmaster.sqliteConnection.prepareStatement("SELECT * FROM generation_tasks") savedTasksStatement.execute() val res = savedTasksStatement.resultSet while (res.next()) { try { - if (res.getBoolean("autostart")) { - val id = res.getInt("id") - val world = server.getWorld(res.getString("world")) - val center = world!!.getChunkAt(res.getInt("center_x"), res.getInt("center_z")) - val last = world.getChunkAt(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) - } + val id = res.getInt("id") + val world = server.getWorld(res.getString("world")) + val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z")) + 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) } } catch (error: NullPointerException) { - server.consoleSender.sendMessage("Failed to load Task ${res.getInt("id")}.") + chunkmaster.logger.severe("Failed to load Task ${res.getInt("id")}.") } } savedTasksStatement.close() - chunkmaster.logger.info("${tasks.size} saved tasks loaded.") + if (tasks.isNotEmpty()) { + chunkmaster.logger.info("${tasks.size} saved tasks loaded.") + } } /** @@ -208,22 +223,27 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server for (task in tasks) { try { val genTask = task.generationTask - server.consoleSender.sendMessage("""Task #${task.id} running for "${genTask.world.name}". + chunkmaster.logger.info( + """Task #${task.id} running for "${genTask.world.name}". |Progress ${task.generationTask.count} chunks - |${if (task.generationTask.stopAfter > 0)"(${(task.generationTask.count.toDouble()/ - task.generationTask.stopAfter.toDouble())*100}%)" else ""}. - |Last Chunk: ${genTask.lastChunk.x}, ${genTask.lastChunk.z}""".trimMargin("|").replace('\n', ' ')) - val updateStatement = chunkmaster.sqliteConnection.prepareStatement(""" + |${if (task.generationTask.stopAfter > 0) "(${"%.2f".format((task.generationTask.count.toDouble() / + task.generationTask.stopAfter.toDouble()) * 100)}%)" else ""}. + | Speed: ${"%.1f".format(task.generationSpeed)} chunks/sec, + |Last Chunk: ${genTask.lastChunk.x}, ${genTask.lastChunk.z}""".trimMargin("|").replace('\n', ' ') + ) + val updateStatement = chunkmaster.sqliteConnection.prepareStatement( + """ UPDATE generation_tasks SET last_x = ?, last_z = ? WHERE id = ? - """.trimIndent()) + """.trimIndent() + ) updateStatement.setInt(1, genTask.lastChunk.x) updateStatement.setInt(2, genTask.lastChunk.z) updateStatement.setInt(3, task.id) updateStatement.execute() updateStatement.close() } catch (error: Exception) { - server.consoleSender.sendMessage("Exception when saving task progress ${error.message}") + chunkmaster.logger.warning("Exception when saving task progress ${error.message}") } } } @@ -232,10 +252,15 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server * Creates a new generation task. This method is used to create a task depending * on the server type (Paper/Spigot). */ - private fun createGenerationTask(world: World, center: Chunk, start: Chunk, stopAfter: Int): GenerationTask { + private fun createGenerationTask( + world: World, + center: ChunkCoordinates, + start: ChunkCoordinates, + stopAfter: Int + ): GenerationTask { return if (PaperLib.isPaper()) { GenerationTaskPaper(chunkmaster, world, center, start, stopAfter) - } else { + } else { GenerationTaskSpigot(chunkmaster, world, center, start, stopAfter) } } 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 91f8575..6aa5e63 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTask.kt @@ -8,7 +8,8 @@ import org.bukkit.World /** * Interface for generation tasks. */ -abstract class GenerationTask(plugin: Chunkmaster, centerChunk: Chunk, startChunk: Chunk) : Runnable { +abstract class GenerationTask(plugin: Chunkmaster, centerChunk: ChunkCoordinates, startChunk: ChunkCoordinates) : + Runnable { abstract val stopAfter: Int abstract val world: World @@ -19,9 +20,10 @@ abstract class GenerationTask(plugin: Chunkmaster, centerChunk: Chunk, startChun Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z)) protected val loadedChunks: HashSet = HashSet() protected var lastChunkCoords = ChunkCoordinates(startChunk.x, startChunk.z) - protected val chunkSkips = plugin.config.getInt("generation.chunks-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 maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks") + protected val chunksPerStep = plugin.config.getInt("generation.chunks-per-step") protected var endReachedCallback: (() -> Unit)? = null private set @@ -34,11 +36,12 @@ abstract class GenerationTask(plugin: Chunkmaster, centerChunk: Chunk, startChun val nextChunkCoords = spiral.next() return ChunkCoordinates(nextChunkCoords.first, nextChunkCoords.second) } - var lastChunk: Chunk = startChunk + + val lastChunk: Chunk get() { return world.getChunkAt(lastChunkCoords.x, lastChunkCoords.z) } - private set + val nextChunk: Chunk get() { val next = nextChunkCoordinates 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 2a49321..278ff37 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskPaper.kt @@ -8,7 +8,7 @@ import io.papermc.lib.PaperLib class GenerationTaskPaper( private val plugin: Chunkmaster, override val world: World, - centerChunk: Chunk, private val startChunk: Chunk, + centerChunk: ChunkCoordinates, private val startChunk: ChunkCoordinates, override val stopAfter: Int = -1 ) : GenerationTask(plugin, centerChunk, startChunk) { @@ -52,7 +52,15 @@ class GenerationTaskPaper( } if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { - pendingChunks.add(PaperLib.getChunkAtAsync(world, chunk.x, chunk.z, true)) + for (i in 0 until minOf(chunksPerStep, (stopAfter - count) - 1)) { + if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { + pendingChunks.add(PaperLib.getChunkAtAsync(world, chunk.x, chunk.z, true)) + } + chunk = nextChunkCoordinates + } + if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { + pendingChunks.add(PaperLib.getChunkAtAsync(world, chunk.x, chunk.z, true)) + } } lastChunkCoords = chunk count = spiral.count // set the count to the more accurate spiral count @@ -66,6 +74,13 @@ class GenerationTaskPaper( * This unloads all chunks that were generated but not unloaded yet. */ override fun cancel() { + unloadAllChunks() + } + + /** + * Cancels all pending chunks and unloads all loaded chunks. + */ + 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 ea71d4d..6ef2dab 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationTaskSpigot.kt @@ -6,7 +6,7 @@ import org.bukkit.World class GenerationTaskSpigot( private val plugin: Chunkmaster, override val world: World, - centerChunk: Chunk, private val startChunk: Chunk, + centerChunk: ChunkCoordinates, private val startChunk: ChunkCoordinates, override val stopAfter: Int = -1 ) : GenerationTask(plugin, centerChunk, startChunk) { @@ -37,9 +37,15 @@ class GenerationTaskSpigot( return } - val chunk = nextChunkCoordinates + var chunk = nextChunkCoordinates if (!world.isChunkGenerated(chunk.x, chunk.z)) { + for (i in 0 until minOf(chunksPerStep, stopAfter - count)) { + val chunkInstance = world.getChunkAt(chunk.x, chunk.z) + chunkInstance.load(true) + loadedChunks.add(chunkInstance) + chunk = nextChunkCoordinates + } val chunkInstance = world.getChunkAt(chunk.x, chunk.z) chunkInstance.load(true) loadedChunks.add(chunkInstance) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/RunningTaskEntry.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/RunningTaskEntry.kt index a53d89a..cc4c294 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/RunningTaskEntry.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/RunningTaskEntry.kt @@ -7,6 +7,29 @@ class RunningTaskEntry( val task: BukkitTask, override val generationTask: GenerationTask ) : TaskEntry { + + private var lastProgress: Pair? = null + + /** + * Returns the generation Speed + */ + val generationSpeed: Double? + get() { + var generationSpeed: Double? = null + if (lastProgress != null) { + val chunkDiff = generationTask.count - lastProgress!!.second + val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble()/1000 + generationSpeed = chunkDiff.toDouble()/timeDiff + } + lastProgress = Pair(System.currentTimeMillis(), generationTask.count) + return generationSpeed + } + + init { + lastProgress = Pair(System.currentTimeMillis(), generationTask.count) + } + + override fun cancel() { task.cancel() generationTask.cancel() diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 350e596..7ee3e97 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.11-beta' +version: '0.12-beta' description: Chunk commands plugin. author: Trivernis website: trivernis.net @@ -9,7 +9,14 @@ commands: chunkmaster: description: Main command permission: chunkmaster.chunkmaster - usage: /chunkmaster + usage: | + / generate [, ] - generates chunks starting from the spawn until the chunk-count is reached + / cancel - cancels the generation task with the task-id + / list - lists all running and paused generation tasks + / pause - pauses all generation tasks + / resume - resumes all generation tasks + / reload - reloads the configuration and restarts all tasks + / tpchunk - teleports you to the chunk with the given chunk coordinates aliases: - chm - chunkm @@ -33,6 +40,9 @@ permissions: chunkmaster.reload: description: Allows the reload subcommand. default: op + chunkmaster.tpchunk: + description: Allows the tpchunk subcommand. + default: op chunkmaster.chunkmaster: description: Allows Chunkmaster commands. default: op