diff --git a/README.md b/README.md index bc9ac9d..0fcf371 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,18 @@ # chunkmaster -Work in Progress. Wait for first release. +This plugin can be used to pre-generate the region of a world around the spawn chunk. The plugin provides the commands -Goal is a plugin that pregenerates chunks around the world spawn. \ No newline at end of file +- /generate - Pre-generates chunks in the current world +- /listgentasks - Lists all running generation tasks (and their ids) +- /removegentask - Removes a generation task (stops it permanently) + +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. + +## Future Features + +- pause generation tasks until restarted by command +- configure the tps pause limit +- specify how many chunks to generate for a task \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt index 5b74044..982e103 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/Chunkmaster.kt @@ -2,9 +2,11 @@ package net.trivernis.chunkmaster import net.trivernis.chunkmaster.commands.CommandGenerate import net.trivernis.chunkmaster.commands.CommandListGenTasks +import net.trivernis.chunkmaster.commands.CommandRemoveGenTask import net.trivernis.chunkmaster.lib.GenerationManager import net.trivernis.chunkmaster.lib.Spiral import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.scheduler.BukkitTask import java.lang.Exception import java.sql.Connection import java.sql.DriverManager @@ -14,7 +16,13 @@ class Chunkmaster: JavaPlugin() { lateinit var sqliteConnection: Connection var dbname: String? = null lateinit var generationManager: GenerationManager + private lateinit var tpsTask: BukkitTask + var mspt = 50L // keep track of the milliseconds per tick + get() = field + /** + * On enable of the plugin + */ override fun onEnable() { configure() initDatabase() @@ -22,15 +30,28 @@ class Chunkmaster: JavaPlugin() { generationManager.init() getCommand("generate")?.setExecutor(CommandGenerate(this)) getCommand("listgentasks")?.setExecutor(CommandListGenTasks(this)) + getCommand("removegentask")?.setExecutor(CommandRemoveGenTask(this)) server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this) + tpsTask = server.scheduler.runTaskTimer(this, Runnable { + val start = System.currentTimeMillis() + server.scheduler.runTaskLater(this, Runnable { + mspt = System.currentTimeMillis() - start + }, 1) + }, 1, 300) } + /** + * Stop all tasks and close database connection on disable + */ override fun onDisable() { logger.info("Stopping all generation tasks...") generationManager.stopAll() sqliteConnection.close() } + /** + * Cofigure the config file + */ private fun configure() { dataFolder.mkdir() config.options().copyDefaults(true) @@ -52,7 +73,8 @@ class Chunkmaster: JavaPlugin() { center_z integer NOT NULL DEFAULT 0, last_x integer NOT NULL DEFAULT 0, last_z integer NOT NULL DEFAULT 0, - world text UNIQUE NOT NULL DEFAULT 'world' + world text UNIQUE NOT NULL DEFAULT 'world', + autostart integer DEFAULT 1 ); """.trimIndent()) createTableStatement.execute() diff --git a/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt b/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt index f000d88..2dc94fe 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/ChunkmasterEvents.kt @@ -1,6 +1,7 @@ package net.trivernis.chunkmaster 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 @@ -10,7 +11,7 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server /** * Autostart generation tasks */ - fun onPlayerQuit(event: PlayerQuitEvent) { + @EventHandler fun onPlayerQuit(event: PlayerQuitEvent) { if (server.onlinePlayers.size == 1 && server.onlinePlayers.contains(event.player) || server.onlinePlayers.isEmpty()) { chunkmaster.generationManager.startAll() @@ -21,7 +22,7 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server /** * Autostop generation tasks */ - fun onPlayerJoin(event: PlayerJoinEvent) { + @EventHandler fun onPlayerJoin(event: PlayerJoinEvent) { if (server.onlinePlayers.size == 1 || server.onlinePlayers.isEmpty()) { chunkmaster.generationManager.stopAll() chunkmaster.logger.info("Stopping generation tasks because of player join.") diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandListGenTasks.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandListGenTasks.kt index aa18df5..76c2f47 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandListGenTasks.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandListGenTasks.kt @@ -15,7 +15,7 @@ class CommandListGenTasks(private val chunkmaster: Chunkmaster): CommandExecutor val runningTasks = chunkmaster.generationManager.tasks val response = ComponentBuilder("Currently running generation tasks").color(ChatColor.BLUE) for (task in runningTasks) { - response.append("\n - #${task.id}: ${task.generationTask.world}, Progress ${task.generationTask.count}") + response.append("\n - #${task.id}: ${task.generationTask.world.name}, Progress ${task.generationTask.count}") } response.color(ChatColor.GREEN) sender.spigot().sendMessage(*response.create()) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandRemoveGenTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandRemoveGenTask.kt new file mode 100644 index 0000000..f9dd25b --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandRemoveGenTask.kt @@ -0,0 +1,19 @@ +package net.trivernis.chunkmaster.commands + +import net.trivernis.chunkmaster.Chunkmaster +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender + +class CommandRemoveGenTask(private val chunkmaster: Chunkmaster): CommandExecutor { + override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { + return if (command.testPermission(sender) && args.size == 1) { + chunkmaster.generationManager.removeTask(args[1].toInt()) + sender.sendMessage("Task ${args[1].toInt()} canceled.") + true + } else { + sender.sendMessage("Invalid argument.") + false + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt index 3e753aa..901f3da 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationManager.kt @@ -47,13 +47,33 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server /** * Resumes a generation task */ - fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int) { + private fun resumeTask(world: World, center: Chunk, last: Chunk, id: Int) { chunkmaster.logger.info("Resuming chunk generation task for world \"${world.name}\"") val generationTask = GenerationTask(chunkmaster, world, center, last) val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, 2) tasks.add(TaskEntry(id, task, generationTask)) } + /** + * Stops a running generation task. + */ + fun removeTask(id: Int) { + val taskEntry = this.tasks.find {it.id == id} + if (taskEntry != null) { + taskEntry.generationTask.cancel() + taskEntry.task.cancel() + if (taskEntry.task.isCancelled) { + tasks.remove(taskEntry) + } + val setAutostart = chunkmaster.sqliteConnection.prepareStatement(""" + UPDATE TABLE generation_tasks SET autostart = 0 WHERE id = ? + """.trimIndent()) + setAutostart.setInt(1, id) + setAutostart.execute() + setAutostart.close() + } + } + /** * Init * Loads tasks from the database and resumes them @@ -75,6 +95,10 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server for (task in tasks) { task.generationTask.cancel() task.task.cancel() + if (task.task.isCancelled) { + tasks.remove(task) + } + chunkmaster.logger.info("Canceled task #${task.id}") } } @@ -88,11 +112,15 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server val res = savedTasksStatement.resultSet while (res.next()) { try { - 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")) - resumeTask(world, center, last, id) + 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")) + if (this.tasks.find {it.id == id} == null) { + resumeTask(world, center, last, id) + } + } } catch (error: NullPointerException) { server.consoleSender.sendMessage("Failed to load Task ${res.getInt("id")}.") } diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt index a271503..1908f84 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/GenerationTask.kt @@ -15,26 +15,36 @@ class GenerationTask(private val plugin: Chunkmaster, val world: World, var lastChunk: Chunk = startChunk get() = field + /** + * Runs the generation task. Every Iteration the next chunk will be generated if + * it hasn't been generated already. + */ override fun run() { - if (loadedChunks.size > 10) { - for (chunk in loadedChunks) { - if (chunk.isLoaded) { - chunk.unload(true) + if (plugin.mspt < 500L) { // pause when tps < 2 + if (loadedChunks.size > 10) { + for (chunk in loadedChunks) { + if (chunk.isLoaded) { + chunk.unload(true) + } } - } - } else { - val nextChunkCoords = spiral.next() - val chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second) + } else { + val nextChunkCoords = spiral.next() + val chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second) - if (!world.isChunkGenerated(chunk.x, chunk.z)) { - chunk.load(true) - loadedChunks.add(chunk) + if (!world.isChunkGenerated(chunk.x, chunk.z)) { + chunk.load(true) + loadedChunks.add(chunk) + } + lastChunk = chunk + count++ } - lastChunk = chunk - count++ } } + /** + * Cancels the generation task. + * This unloads all chunks that were generated but not unloaded yet. + */ fun cancel() { for (chunk in loadedChunks) { if (chunk.isLoaded) { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/Spiral.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/Spiral.kt index 0aa147e..cf98481 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/Spiral.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/Spiral.kt @@ -2,7 +2,7 @@ package net.trivernis.chunkmaster.lib import kotlin.math.abs -class Spiral(private val center: Pair, private val start: Pair) { +class Spiral(private val center: Pair, start: Pair) { var currentPos = start var direction = 0 var count = 0 @@ -12,15 +12,12 @@ class Spiral(private val center: Pair, private val start: Pair { if (count == 0 && currentPos != center) { - val distances = getDistances(center, currentPos) - - if (distances.second < distances.first && distances.first > 0) { - direction = 1 - } else if (distances.first > distances.second && distances.second < 0) { - direction = 2 - } else if (distances.second > distances.first && distances.first < 0) { - direction = 3 - } + // simulate the spiral to get the correct direction + // TODO: Improve performance of this workaround (replace it with acutal stuff) + val simSpiral = Spiral(center, center) + while (simSpiral.next() != currentPos); + direction = simSpiral.direction + count = simSpiral.count } if (count == 1) { // because of the center behaviour count ++ diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0b88a55..c8bb5b6 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -14,16 +14,24 @@ commands: description: Lists all generation tasks permission: chunkmaster.listgentasks usage: /listgentasks + removegentask: + description: Removes the generation task with the specified id + permission: chunkmaster.removegentask + usage: /removegentask {task-id} permissions: cunkmaster.generate: - description: Allows generate command + description: Allows generate command. default: op chunkmaster.listgentasks: - description: Lists all generation tasks + description: Allows the listgentask command. + default: op + chunkmaster.removegentask: + description: Allows the removegentask command. default: op chunkmaster.*: description: Wildcard permission default: op children: - chunkmaster.generate - - chunkmaster.listgentasks \ No newline at end of file + - chunkmaster.listgentasks + - chunkmaster.removegentask \ No newline at end of file