Merge pull request #8 from Trivernis/develop

Beta 1.12 Changes
pull/10/head v0.12-beta
Trivernis 5 years ago committed by GitHub
commit 707ba0e8cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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) 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 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 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 ## 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`.
- `/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 list` Lists all running generation tasks
- `/chunkmaster cancel <Task id>` Cancels the generation task with the specified id (if it is running). - `/chunkmaster cancel <Task id>` 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 pause` Pauses all generation tasks until the resume command is executed.
- `/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.
## Config ## Config
@ -39,13 +40,18 @@ generation:
# The value should be a positive integer. # The value should be a positive integer.
period: 2 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 # Paper Only
# The number of already generated chunks that will be skipped for each step. # 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 # Notice that these still have a performance impact because the server needs to check
# if the chunk is generated. # if the chunk is generated.
# Higher values mean faster generation but greater performance impact. # Higher values mean faster generation but greater performance impact.
# The value should be a positive integer. # 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 # The maximum milliseconds per tick the server is allowed to have
# during the cunk generation process. # during the cunk generation process.

@ -22,7 +22,7 @@ idea {
} }
group "net.trivernis" group "net.trivernis"
version "0.11-beta" version "0.12-beta"
sourceCompatibility = 1.8 sourceCompatibility = 1.8

@ -32,8 +32,8 @@ class Chunkmaster: JavaPlugin() {
generationManager = GenerationManager(this, server) generationManager = GenerationManager(this, server)
generationManager.init() generationManager.init()
getCommand("chunkmaster")?.setExecutor(CommandChunkmaster(this, server))
getCommand("chunkmaster")?.aliases = mutableListOf("chm", "chunkm", "cmaster") getCommand("chunkmaster")?.aliases = mutableListOf("chm", "chunkm", "cmaster")
getCommand("chunkmaster")?.setExecutor(CommandChunkmaster(this, server))
server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this) server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this)
@ -60,7 +60,8 @@ class Chunkmaster: JavaPlugin() {
private fun configure() { private fun configure() {
dataFolder.mkdir() dataFolder.mkdir()
config.addDefault("generation.period", 2L) 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.mspt-pause-threshold", 500L)
config.addDefault("generation.pause-on-join", true) config.addDefault("generation.pause-on-join", true)
config.addDefault("generation.max-pending-chunks", 10) config.addDefault("generation.max-pending-chunks", 10)

@ -1,10 +1,12 @@
package net.trivernis.chunkmaster package net.trivernis.chunkmaster
import net.trivernis.chunkmaster.lib.generation.GenerationTaskPaper
import org.bukkit.Server import org.bukkit.Server
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.event.world.WorldSaveEvent
class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server) : Listener { 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() server.onlinePlayers.isEmpty()
) { ) {
if (!playerPaused) { if (!playerPaused) {
chunkmaster.generationManager.resumeAll() if (chunkmaster.generationManager.pausedTasks.isNotEmpty()) {
chunkmaster.logger.info("Server is empty. Resuming chunk generation tasks.") chunkmaster.logger.info("Server is empty. Resuming chunk generation tasks.")
}
chunkmaster.generationManager.resumeAll()
} else if (chunkmaster.generationManager.paused){ } else if (chunkmaster.generationManager.paused){
chunkmaster.logger.info("Generation was manually paused. Not resuming automatically.") chunkmaster.logger.info("Generation was manually paused. Not resuming automatically.")
playerPaused = chunkmaster.generationManager.paused playerPaused = chunkmaster.generationManager.paused
@ -40,9 +44,24 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server
fun onPlayerJoin(event: PlayerJoinEvent) { fun onPlayerJoin(event: PlayerJoinEvent) {
if (pauseOnJoin) { if (pauseOnJoin) {
if (server.onlinePlayers.size == 1 || server.onlinePlayers.isEmpty()) { 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 playerPaused = chunkmaster.generationManager.paused
chunkmaster.generationManager.pauseAll() 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()
} }
} }
} }

@ -7,6 +7,7 @@ import net.trivernis.chunkmaster.lib.Subcommand
import org.bukkit.command.Command import org.bukkit.command.Command
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.Player import org.bukkit.entity.Player
import kotlin.math.pow
class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand { class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "generate" override val name = "generate"
@ -23,9 +24,19 @@ class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand {
if (args.size == 1) { if (args.size == 1) {
return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 }
.map {it.name}.toMutableList() .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<String>().toMutableList() return emptyList<String>().toMutableList()
} }
val units = listOf("blockradius", "radius", "diameter")
/** /**
* Creates a new generation task for the world and chunk count. * 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.isNotEmpty()) {
if (args[0].toIntOrNull() != null) { if (args[0].toIntOrNull() != null) {
stopAfter = args[0].toInt() stopAfter = args[0].toInt()
worldName = sender.world.name
} else { } else {
worldName = args[0] worldName = args[0]
} }
if (args.size > 1 && args[1].toIntOrNull() != null) { if (args.size > 1) {
if (args[1].toIntOrNull() != null) {
stopAfter = args[1].toInt() 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 { } else {
worldName = sender.world.name worldName = sender.world.name
@ -49,15 +70,48 @@ class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand {
} else { } else {
if (args.isNotEmpty()) { if (args.isNotEmpty()) {
worldName = args[0] worldName = args[0]
if (args.size > 1 && args[1].toIntOrNull() != null) { if (args.size > 1) {
if (args[1].toIntOrNull() != null) {
stopAfter = args[1].toInt() stopAfter = args[1].toInt()
} }
}
if (args.size > 2 && args[2] in units) {
stopAfter = getStopAfter(stopAfter, args[2])
}
} else { } else {
sender.spigot().sendMessage( sender.spigot().sendMessage(
*ComponentBuilder("You need to provide a world name").color(ChatColor.RED).create()) *ComponentBuilder("You need to provide a world name").color(ChatColor.RED).create())
return false 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 world = chunkmaster.server.getWorld(worldName)
val allTasks = chunkmaster.generationManager.allTasks val allTasks = chunkmaster.generationManager.allTasks
return if (world != null && (allTasks.find { it.generationTask.world == world }) == null) { return if (world != null && (allTasks.find { it.generationTask.world == world }) == null) {

@ -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<String>
): MutableList<String> {
return emptyList<String>().toMutableList()
}
/**
* Reload command to reload the config and restart the tasks.
*/
override fun execute(sender: CommandSender, args: List<String>): 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
}
}

@ -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<String>
): MutableList<String> {
return emptyList<String>().toMutableList()
}
/**
* Teleports the player to a save location in the chunk
*/
override fun execute(sender: CommandSender, args: List<String>): 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
}
}
}

@ -10,7 +10,7 @@ import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.command.TabCompleter 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 { TabCompleter {
private val commands = HashMap<String, Subcommand>() private val commands = HashMap<String, Subcommand>()
@ -25,7 +25,7 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
MutableList<String> { MutableList<String> {
if (args.size == 1) { if (args.size == 1) {
return commands.keys.filter { it.indexOf(args[0]) == 0 }.toMutableList() return commands.keys.filter { it.indexOf(args[0]) == 0 }.toMutableList()
} else if (args.isNotEmpty()){ } else if (args.isNotEmpty()) {
if (commands.containsKey(args[0])) { if (commands.containsKey(args[0])) {
val commandEntry = commands[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<out String>): Boolean { override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
if (args.isNotEmpty()) { if (args.isNotEmpty()) {
when (args[0]) {
"reload" -> {
chunkmaster.reloadConfig()
sender.sendMessage("Configuration file reloaded.")
}
else -> {
if (sender.hasPermission("chunkmaster.${args[0]}")) { if (sender.hasPermission("chunkmaster.${args[0]}")) {
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 {
sender.spigot().sendMessage(*ComponentBuilder("Subcommand ").color(ChatColor.RED) sender.spigot().sendMessage(
.append(args[0]).color(ChatColor.GREEN).append(" not found").color(ChatColor.RED).create()) *ComponentBuilder("Subcommand ").color(ChatColor.RED)
.append(args[0]).color(ChatColor.GREEN).append(" not found").color(ChatColor.RED).create()
)
false false
} }
} else { } else {
sender.spigot().sendMessage(*ComponentBuilder("You do not have permission!") sender.spigot().sendMessage(
.color(ChatColor.RED).create()) *ComponentBuilder("You do not have permission!")
} .color(ChatColor.RED).create()
} )
} }
return true return true
} else { } 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) val cmdGenerate = CmdGenerate(chunkmaster)
commands[cmdGenerate.name] = cmdGenerate commands[cmdGenerate.name] = cmdGenerate
@ -81,5 +80,11 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
val cmdList = CmdList(chunkmaster) val cmdList = CmdList(chunkmaster)
commands[cmdList.name] = cmdList commands[cmdList.name] = cmdList
val cmdReload = CmdReload(chunkmaster)
commands[cmdReload.name] = cmdReload
val cmdTpChunk = CmdTpChunk()
commands[cmdTpChunk.name] = cmdTpChunk
} }
} }

@ -15,8 +15,7 @@ class SqlUpdateManager(private val connnection: Connection, private val chunkmas
Pair("last_x", "integer NOT NULL DEFAULT 0"), Pair("last_x", "integer NOT NULL DEFAULT 0"),
Pair("last_z", "integer NOT NULL DEFAULT 0"), Pair("last_z", "integer NOT NULL DEFAULT 0"),
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("autostart", "integer DEFAULT 1")
) )
) )
) )

@ -28,13 +28,15 @@ 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 = world.getChunkAt(world.spawnLocation) val centerChunk = ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z)
val generationTask = createGenerationTask(world, centerChunk, centerChunk, stopAfter) 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) INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, stop_after)
values (?, ?, ?, ?, ?, ?) values (?, ?, ?, ?, ?, ?)
""") """
)
insertStatement.setInt(1, centerChunk.x) insertStatement.setInt(1, centerChunk.x)
insertStatement.setInt(2, centerChunk.z) insertStatement.setInt(2, centerChunk.z)
insertStatement.setInt(3, centerChunk.x) insertStatement.setInt(3, centerChunk.x)
@ -43,9 +45,11 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
insertStatement.setInt(6, stopAfter) insertStatement.setInt(6, stopAfter)
insertStatement.execute() insertStatement.execute()
val getIdStatement = chunkmaster.sqliteConnection.prepareStatement(""" val getIdStatement = chunkmaster.sqliteConnection.prepareStatement(
"""
SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1 SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1
""".trimIndent()) """.trimIndent()
)
getIdStatement.execute() getIdStatement.execute()
val result = getIdStatement.resultSet val result = getIdStatement.resultSet
result.next() result.next()
@ -55,13 +59,15 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
getIdStatement.close() getIdStatement.close()
generationTask.onEndReached { generationTask.onEndReached {
server.consoleSender.sendMessage("Task #${id} finished after ${generationTask.count} chunks.") chunkmaster.logger.info("Task #${id} finished after ${generationTask.count} chunks.")
removeTask(id) removeTask(id)
} }
if (!paused) { if (!paused) {
val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, val task = server.scheduler.runTaskTimer(
chunkmaster.config.getLong("generation.period")) chunkmaster, generationTask, 200, // 10 sec delay
chunkmaster.config.getLong("generation.period")
)
tasks.add(RunningTaskEntry(id, task, generationTask)) tasks.add(RunningTaskEntry(id, task, generationTask))
} else { } else {
pausedTasks.add(PausedTaskEntry(id, generationTask)) pausedTasks.add(PausedTaskEntry(id, generationTask))
@ -76,15 +82,23 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
/** /**
* Resumes a generation task * 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) { if (!paused) {
chunkmaster.logger.info("Resuming chunk generation task for world \"${world.name}\"") chunkmaster.logger.info("Resuming chunk generation task for world \"${world.name}\"")
val generationTask = createGenerationTask(world, center, last, stopAfter) val generationTask = createGenerationTask(world, center, last, stopAfter)
val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, val task = server.scheduler.runTaskTimer(
chunkmaster.config.getLong("generation.period")) chunkmaster, generationTask, 200, // 10 sec delay
chunkmaster.config.getLong("generation.period")
)
tasks.add(RunningTaskEntry(id, task, generationTask)) tasks.add(RunningTaskEntry(id, task, generationTask))
generationTask.onEndReached { generationTask.onEndReached {
server.consoleSender.sendMessage("Task #${id} finished after ${generationTask.count} chunks.") chunkmaster.logger.info("Task #${id} finished after ${generationTask.count} chunks.")
removeTask(id) removeTask(id)
} }
} }
@ -95,15 +109,17 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
*/ */
fun removeTask(id: Int): Boolean { fun removeTask(id: Int): Boolean {
val taskEntry: TaskEntry? = if (this.paused) { val taskEntry: TaskEntry? = if (this.paused) {
this.pausedTasks.find {it.id == id} this.pausedTasks.find { it.id == id }
} else { } else {
this.tasks.find {it.id == id} this.tasks.find { it.id == id }
} }
if (taskEntry != null) { if (taskEntry != null) {
taskEntry.cancel() taskEntry.cancel()
val deleteTask = chunkmaster.sqliteConnection.prepareStatement(""" val deleteTask = chunkmaster.sqliteConnection.prepareStatement(
"""
DELETE FROM generation_tasks WHERE id = ?; DELETE FROM generation_tasks WHERE id = ?;
""".trimIndent()) """.trimIndent()
)
deleteTask.setInt(1, taskEntry.id) deleteTask.setInt(1, taskEntry.id)
deleteTask.execute() deleteTask.execute()
deleteTask.close() deleteTask.close()
@ -133,7 +149,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
if (server.onlinePlayers.isEmpty()) { if (server.onlinePlayers.isEmpty()) {
startAll() // run startAll after 10 seconds if empty startAll() // run startAll after 10 seconds if empty
} }
}, 200) }, 600)
} }
/** /**
@ -157,29 +173,28 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
* Starts all generation tasks. * Starts all generation tasks.
*/ */
fun startAll() { fun startAll() {
chunkmaster.logger.info("Loading saved chunk generation tasks...")
val savedTasksStatement = chunkmaster.sqliteConnection.prepareStatement("SELECT * FROM generation_tasks") val savedTasksStatement = chunkmaster.sqliteConnection.prepareStatement("SELECT * FROM generation_tasks")
savedTasksStatement.execute() savedTasksStatement.execute()
val res = savedTasksStatement.resultSet val res = savedTasksStatement.resultSet
while (res.next()) { while (res.next()) {
try { try {
if (res.getBoolean("autostart")) {
val id = res.getInt("id") val id = res.getInt("id")
val world = server.getWorld(res.getString("world")) val world = server.getWorld(res.getString("world"))
val center = world!!.getChunkAt(res.getInt("center_x"), res.getInt("center_z")) val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z"))
val last = world.getChunkAt(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)
}
} }
} catch (error: NullPointerException) { } 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() savedTasksStatement.close()
if (tasks.isNotEmpty()) {
chunkmaster.logger.info("${tasks.size} saved tasks loaded.") chunkmaster.logger.info("${tasks.size} saved tasks loaded.")
} }
}
/** /**
* Pauses all tasks * Pauses all tasks
@ -208,22 +223,27 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
for (task in tasks) { for (task in tasks) {
try { try {
val genTask = task.generationTask 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 |Progress ${task.generationTask.count} chunks
|${if (task.generationTask.stopAfter > 0)"(${(task.generationTask.count.toDouble()/ |${if (task.generationTask.stopAfter > 0) "(${"%.2f".format((task.generationTask.count.toDouble() /
task.generationTask.stopAfter.toDouble())*100}%)" else ""}. task.generationTask.stopAfter.toDouble()) * 100)}%)" else ""}.
|Last Chunk: ${genTask.lastChunk.x}, ${genTask.lastChunk.z}""".trimMargin("|").replace('\n', ' ')) | Speed: ${"%.1f".format(task.generationSpeed)} chunks/sec,
val updateStatement = chunkmaster.sqliteConnection.prepareStatement(""" |Last Chunk: ${genTask.lastChunk.x}, ${genTask.lastChunk.z}""".trimMargin("|").replace('\n', ' ')
)
val updateStatement = chunkmaster.sqliteConnection.prepareStatement(
"""
UPDATE generation_tasks SET last_x = ?, last_z = ? UPDATE generation_tasks SET last_x = ?, last_z = ?
WHERE id = ? WHERE id = ?
""".trimIndent()) """.trimIndent()
)
updateStatement.setInt(1, genTask.lastChunk.x) updateStatement.setInt(1, genTask.lastChunk.x)
updateStatement.setInt(2, genTask.lastChunk.z) updateStatement.setInt(2, genTask.lastChunk.z)
updateStatement.setInt(3, task.id) updateStatement.setInt(3, task.id)
updateStatement.execute() updateStatement.execute()
updateStatement.close() updateStatement.close()
} catch (error: Exception) { } catch (error: Exception) {
server.consoleSender.sendMessage("Exception when saving task progress ${error.message}") chunkmaster.logger.warning("Exception when saving task progress ${error.message}")
} }
} }
} }
@ -232,7 +252,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
* 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).
*/ */
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()) { return if (PaperLib.isPaper()) {
GenerationTaskPaper(chunkmaster, world, center, start, stopAfter) GenerationTaskPaper(chunkmaster, world, center, start, stopAfter)
} else { } else {

@ -8,7 +8,8 @@ import org.bukkit.World
/** /**
* Interface for generation tasks. * 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 stopAfter: Int
abstract val world: World 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)) 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) 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 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 var endReachedCallback: (() -> Unit)? = null protected var endReachedCallback: (() -> Unit)? = null
private set private set
@ -34,11 +36,12 @@ abstract class GenerationTask(plugin: Chunkmaster, centerChunk: Chunk, startChun
val nextChunkCoords = spiral.next() val nextChunkCoords = spiral.next()
return ChunkCoordinates(nextChunkCoords.first, nextChunkCoords.second) return ChunkCoordinates(nextChunkCoords.first, nextChunkCoords.second)
} }
var lastChunk: Chunk = startChunk
val lastChunk: Chunk
get() { get() {
return world.getChunkAt(lastChunkCoords.x, lastChunkCoords.z) return world.getChunkAt(lastChunkCoords.x, lastChunkCoords.z)
} }
private set
val nextChunk: Chunk val nextChunk: Chunk
get() { get() {
val next = nextChunkCoordinates val next = nextChunkCoordinates

@ -8,7 +8,7 @@ import io.papermc.lib.PaperLib
class GenerationTaskPaper( class GenerationTaskPaper(
private val plugin: Chunkmaster, override val world: World, 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 override val stopAfter: Int = -1
) : GenerationTask(plugin, centerChunk, startChunk) { ) : GenerationTask(plugin, centerChunk, startChunk) {
@ -51,9 +51,17 @@ class GenerationTaskPaper(
} }
} }
if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) {
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)) { if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) {
pendingChunks.add(PaperLib.getChunkAtAsync(world, chunk.x, chunk.z, true)) pendingChunks.add(PaperLib.getChunkAtAsync(world, chunk.x, chunk.z, true))
} }
}
lastChunkCoords = chunk lastChunkCoords = chunk
count = spiral.count // set the count to the more accurate spiral count 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. * This unloads all chunks that were generated but not unloaded yet.
*/ */
override fun cancel() { override fun cancel() {
unloadAllChunks()
}
/**
* Cancels all pending chunks and unloads all loaded chunks.
*/
fun unloadAllChunks() {
for (pendingChunk in pendingChunks) { for (pendingChunk in pendingChunks) {
if (pendingChunk.isDone) { if (pendingChunk.isDone) {
loadedChunks.add(pendingChunk.get()) loadedChunks.add(pendingChunk.get())

@ -6,7 +6,7 @@ import org.bukkit.World
class GenerationTaskSpigot( class GenerationTaskSpigot(
private val plugin: Chunkmaster, override val world: World, 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 override val stopAfter: Int = -1
) : GenerationTask(plugin, centerChunk, startChunk) { ) : GenerationTask(plugin, centerChunk, startChunk) {
@ -37,9 +37,15 @@ class GenerationTaskSpigot(
return return
} }
val chunk = nextChunkCoordinates var chunk = nextChunkCoordinates
if (!world.isChunkGenerated(chunk.x, chunk.z)) { 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) val chunkInstance = world.getChunkAt(chunk.x, chunk.z)
chunkInstance.load(true) chunkInstance.load(true)
loadedChunks.add(chunkInstance) loadedChunks.add(chunkInstance)

@ -7,6 +7,29 @@ class RunningTaskEntry(
val task: BukkitTask, val task: BukkitTask,
override val generationTask: GenerationTask override val generationTask: GenerationTask
) : TaskEntry { ) : TaskEntry {
private var lastProgress: Pair<Long, Int>? = 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() { override fun cancel() {
task.cancel() task.cancel()
generationTask.cancel() generationTask.cancel()

@ -1,6 +1,6 @@
main: net.trivernis.chunkmaster.Chunkmaster main: net.trivernis.chunkmaster.Chunkmaster
name: Chunkmaster name: Chunkmaster
version: '0.11-beta' version: '0.12-beta'
description: Chunk commands plugin. description: Chunk commands plugin.
author: Trivernis author: Trivernis
website: trivernis.net website: trivernis.net
@ -9,7 +9,14 @@ commands:
chunkmaster: chunkmaster:
description: Main command description: Main command
permission: chunkmaster.chunkmaster permission: chunkmaster.chunkmaster
usage: /chunkmaster <generate|list|pause|resume|cancel|reload> usage: |
/<command> generate [<world>, <chunk-count>] - generates chunks starting from the spawn until the chunk-count is reached
/<command> cancel <task-id> - cancels the generation task with the task-id
/<command> list - lists all running and paused generation tasks
/<command> pause - pauses all generation tasks
/<command> resume - resumes all generation tasks
/<command> reload - reloads the configuration and restarts all tasks
/<command> tpchunk <chunkX> <chunkZ> - teleports you to the chunk with the given chunk coordinates
aliases: aliases:
- chm - chm
- chunkm - chunkm
@ -33,6 +40,9 @@ permissions:
chunkmaster.reload: chunkmaster.reload:
description: Allows the reload subcommand. description: Allows the reload subcommand.
default: op default: op
chunkmaster.tpchunk:
description: Allows the tpchunk subcommand.
default: op
chunkmaster.chunkmaster: chunkmaster.chunkmaster:
description: Allows Chunkmaster commands. description: Allows Chunkmaster commands.
default: op default: op

Loading…
Cancel
Save