Bug fixes, config file, more commands

- fixed chunk skips skipping not-generated chunks
- changed command structure to /chunkmaster <operation>
- added config options
- updated readme
pull/4/head v0.9-beta
Trivernis 5 years ago
parent f726f3e15c
commit 52ba24390f

@ -1,17 +1,50 @@
# chunkmaster # chunkmaster
This plugin can be used to pre-generate the region of a world around the spawn chunk. The plugin provides the commands This plugin can be used to pre-generate the region of a world around the spawn chunk.
- `/generate [world] [stopAt]` - Pre-generates chunks in the current world until the world border or the stopAt chunk count is reached.
- `/listgentasks` - Lists all running generation tasks (and their ids)
- `/removegentask [taskId]` - Removes a generation task (stops it permanently)
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.
## Future Features ## 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 list` Lists all running generation tasks
- `/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 resume` Resumes all paused generation tasks.
- `/chunkmaster reload` Reloads the configuration file.
## Config
```yaml
generation:
# The period (in ticks) in which a generation step is run.
# Higher values mean less performance impact but slower generation.
# The value should be a positive integer.
period: 2
# 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
# The maximum milliseconds per tick the server is allowed to have
# during the cunk generation process.
# If the mspt is greather than this, the chunk generation task pauses.
# The value should be a positive integer greater than 50.
mspt-pause-threshold: 500
- pause generation tasks until restarted by command # If the chunk generation process should pause on player join.
- configure the tps pause limit # Notice that playing on a server that constantly generates chunks can be
# very laggy and can cause it to crash.
# You could configure the values above so that the performance impact of the generation
# process is minimal.
# The value should be a boolean <true/false>
pause-on-join: true
```

@ -1,17 +1,13 @@
package net.trivernis.chunkmaster package net.trivernis.chunkmaster
import net.trivernis.chunkmaster.commands.CommandGenerate import net.trivernis.chunkmaster.commands.*
import net.trivernis.chunkmaster.commands.CommandListGenTasks
import net.trivernis.chunkmaster.commands.CommandRemoveGenTask
import net.trivernis.chunkmaster.lib.GenerationManager import net.trivernis.chunkmaster.lib.GenerationManager
import net.trivernis.chunkmaster.lib.Spiral
import net.trivernis.chunkmaster.lib.SqlUpdateManager import net.trivernis.chunkmaster.lib.SqlUpdateManager
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
import java.lang.Exception import java.lang.Exception
import java.sql.Connection import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import kotlin.math.log
class Chunkmaster: JavaPlugin() { class Chunkmaster: JavaPlugin() {
lateinit var sqliteConnection: Connection lateinit var sqliteConnection: Connection
@ -19,7 +15,7 @@ class Chunkmaster: JavaPlugin() {
lateinit var generationManager: GenerationManager lateinit var generationManager: GenerationManager
private lateinit var tpsTask: BukkitTask private lateinit var tpsTask: BukkitTask
var mspt = 50L // keep track of the milliseconds per tick var mspt = 50L // keep track of the milliseconds per tick
get() = field private set
/** /**
* On enable of the plugin * On enable of the plugin
@ -29,10 +25,12 @@ class Chunkmaster: JavaPlugin() {
initDatabase() initDatabase()
generationManager = GenerationManager(this, server) generationManager = GenerationManager(this, server)
generationManager.init() generationManager.init()
getCommand("generate")?.setExecutor(CommandGenerate(this))
getCommand("listgentasks")?.setExecutor(CommandListGenTasks(this)) getCommand("chunkmaster")?.setExecutor(CommandChunkmaster(this, server))
getCommand("removegentask")?.setExecutor(CommandRemoveGenTask(this)) getCommand("chunkmaster")?.aliases = mutableListOf("chm", "chunkm", "cmaster")
server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this) server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this)
tpsTask = server.scheduler.runTaskTimer(this, Runnable { tpsTask = server.scheduler.runTaskTimer(this, Runnable {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
server.scheduler.runTaskLater(this, Runnable { server.scheduler.runTaskLater(this, Runnable {
@ -55,7 +53,12 @@ class Chunkmaster: JavaPlugin() {
*/ */
private fun configure() { private fun configure() {
dataFolder.mkdir() dataFolder.mkdir()
config.addDefault("generation.period", 2L)
config.addDefault("generation.chunks-skips-per-step", 4)
config.addDefault("generation.mspt-pause-threshold", 500L)
config.addDefault("generation.pause-on-join", true)
config.options().copyDefaults(true) config.options().copyDefaults(true)
saveConfig()
} }
/** /**

@ -8,24 +8,32 @@ import org.bukkit.event.player.PlayerQuitEvent
class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server): Listener { class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server): Listener {
private val pauseOnJoin = chunkmaster.config.getBoolean("generation.pause-on-join")
/** /**
* Autostart generation tasks * Autostart generation tasks
*/ */
@EventHandler fun onPlayerQuit(event: PlayerQuitEvent) { @EventHandler fun onPlayerQuit(event: PlayerQuitEvent) {
if (pauseOnJoin) {
if (server.onlinePlayers.size == 1 && server.onlinePlayers.contains(event.player) || if (server.onlinePlayers.size == 1 && server.onlinePlayers.contains(event.player) ||
server.onlinePlayers.isEmpty()) { server.onlinePlayers.isEmpty()) {
if (!chunkmaster.generationManager.paused) {
chunkmaster.generationManager.startAll() chunkmaster.generationManager.startAll()
chunkmaster.logger.info("Server is empty. Starting chunk generation tasks.") chunkmaster.logger.info("Server is empty. Starting chunk generation tasks.")
} }
} }
}
}
/** /**
* Autostop generation tasks * Autostop generation tasks
*/ */
@EventHandler fun onPlayerJoin(event: PlayerJoinEvent) { @EventHandler fun onPlayerJoin(event: PlayerJoinEvent) {
if (pauseOnJoin) {
if (server.onlinePlayers.size == 1 || server.onlinePlayers.isEmpty()) { if (server.onlinePlayers.size == 1 || server.onlinePlayers.isEmpty()) {
chunkmaster.generationManager.stopAll() chunkmaster.generationManager.stopAll()
chunkmaster.logger.info("Stopping generation tasks because of player join.") chunkmaster.logger.info("Stopping generation tasks because of player join.")
} }
} }
} }
}

@ -0,0 +1,28 @@
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.CommandSender
class CmdCancel(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "cancel"
override fun execute(sender: CommandSender, args: List<String>): Boolean {
return if (args.isNotEmpty() && args[0].toIntOrNull() != null) {
if (chunkmaster.generationManager.removeTask(args[0].toInt())) {
sender.sendMessage("Task ${args[0]} canceled.")
true
} else {
sender.spigot().sendMessage(*ComponentBuilder("Task ${args[0]} not found!")
.color(ChatColor.RED).create())
false
}
} else {
sender.spigot().sendMessage(*ComponentBuilder("You need to provide a task id to cancel.")
.color(ChatColor.RED).create())
false
}
}
}

@ -0,0 +1,55 @@
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.CommandSender
import org.bukkit.entity.Player
class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "generate"
override fun execute(sender: CommandSender, args: List<String>): Boolean {
var worldName = ""
var stopAfter = -1
if (sender is Player) {
if (args.isNotEmpty()) {
if (args[0].toIntOrNull() != null) {
stopAfter = args[0].toInt()
} else {
worldName = args[0]
}
if (args.size > 1 && args[1].toIntOrNull() != null) {
stopAfter = args[1].toInt()
}
} else {
worldName = sender.world.name
}
} else {
if (args.isNotEmpty()) {
worldName = args[0]
if (args.size > 1 && args[1].toIntOrNull() != null) {
stopAfter = args[1].toInt()
}
} else {
sender.spigot().sendMessage(
*ComponentBuilder("You need to provide a world name").color(ChatColor.RED).create())
return false
}
}
val world = chunkmaster.server.getWorld(worldName)
return if (world != null) {
chunkmaster.generationManager.addTask(world, stopAfter)
sender.spigot().sendMessage(*ComponentBuilder("Generation task for world ").color(ChatColor.BLUE)
.append(worldName).color(ChatColor.GREEN).append(" until ").color(ChatColor.BLUE)
.append(if (stopAfter > 0) "$stopAfter chunks" else "WorldBorder").color(ChatColor.GREEN)
.append(" successfully created").color(ChatColor.BLUE).create())
true
} else {
sender.spigot().sendMessage(*ComponentBuilder("World ").color(ChatColor.RED)
.append(worldName).color(ChatColor.GREEN).append(" not found!").color(ChatColor.RED).create())
false
}
}
}

@ -0,0 +1,27 @@
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.CommandSender
class CmdList(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "list"
override fun execute(sender: CommandSender, args: List<String>): Boolean {
val runningTasks = chunkmaster.generationManager.tasks
if (runningTasks.isEmpty()) {
sender.spigot().sendMessage(*ComponentBuilder("There are no running generation tasks.")
.color(ChatColor.BLUE).create())
} else {
val response = ComponentBuilder("Currently running generation tasks:").color(ChatColor.WHITE)
for (task in runningTasks) {
response.append("\n - #${task.id}: ${task.generationTask.world.name}, Progress ${task.generationTask.count}")
.color(ChatColor.BLUE)
}
sender.spigot().sendMessage(*response.create())
}
return true
}
}

@ -0,0 +1,27 @@
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.CommandSender
class CmdPause(private val chunkmaster: Chunkmaster) : Subcommand {
override val name: String = "pause"
override fun execute(sender: CommandSender, args: List<String>): Boolean {
return if (!chunkmaster.generationManager.paused) {
chunkmaster.generationManager.pauseAll()
sender.spigot().sendMessage(
*ComponentBuilder("Paused all generation tasks.")
.color(ChatColor.BLUE).create()
)
true
} else {
sender.spigot().sendMessage(
*ComponentBuilder("The generation process is already paused.").color(ChatColor.RED).create()
)
false
}
}
}

@ -0,0 +1,24 @@
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.CommandSender
class CmdResume(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "resume"
override fun execute(sender: CommandSender, args: List<String>): Boolean {
return if (chunkmaster.generationManager.paused) {
chunkmaster.generationManager.resumeAll()
sender.spigot().sendMessage(
*ComponentBuilder("Resumed all generation tasks.").color(ChatColor.BLUE).create())
true
} else {
sender.spigot().sendMessage(
*ComponentBuilder("There are no paused generation tasks.").color(ChatColor.RED).create())
false
}
}
}

@ -0,0 +1,76 @@
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.Server
import org.bukkit.command.Command
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,
TabCompleter {
private val commands = HashMap<String, Subcommand>()
init {
registerCommands()
}
/**
* Tab complete for commands
*/
override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array<out String>):
MutableList<String> {
return commands.keys.filter { it.indexOf(args[0]) == 0 }.toMutableList()
}
/**
* /chunkmaster command to handle all commands
*/
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): 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())
}
}
}
return true
} else {
return false
}
}
fun registerCommands() {
val cmdGenerate = CmdGenerate(chunkmaster)
commands[cmdGenerate.name] = cmdGenerate
val cmdPause = CmdPause(chunkmaster)
commands[cmdPause.name] = cmdPause
val cmdResume = CmdResume(chunkmaster)
commands[cmdResume.name] = cmdResume
val cmdCancel = CmdCancel(chunkmaster)
commands[cmdCancel.name] = cmdCancel
val cmdList = CmdList(chunkmaster)
commands[cmdList.name] = cmdList
}
}

@ -1,54 +0,0 @@
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
import org.bukkit.entity.Player
class CommandGenerate(private val chunkmaster: Chunkmaster): CommandExecutor {
/**
* Start world generation task on commmand
*/
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
if (sender is Player) {
return if (command.testPermission(sender)) {
var stopAfter = -1
if (args.isNotEmpty() && args[0].toIntOrNull() != null) {
stopAfter = args[0].toInt()
}
chunkmaster.generationManager.addTask(sender.world, stopAfter)
sender.sendMessage("Added generation task for world \"${sender.world.name}\"" +
"Stopping after $stopAfter chunks (-1 for world border).")
true
} else {
sender.sendMessage("You do not have permission.")
true
}
} else {
return if (args.size >= 1) {
val world = sender.server.getWorld(args[0])
if (world != null) {
var stopAfter = -1
if (args.size >=2 && args[1].toIntOrNull() != null) {
stopAfter = args[1].toInt()
}
chunkmaster.generationManager.addTask(world, stopAfter)
sender.sendMessage("Added generation task for world \"${world.name}\"" +
"Stopping after $stopAfter chunks (-1 for world border).")
true
} else {
sender.sendMessage("World \"${args[0]}\" not found")
false
}
} else {
sender.sendMessage("You need to specify a world")
false
}
}
}
}

@ -1,24 +0,0 @@
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 org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
class CommandListGenTasks(private val chunkmaster: Chunkmaster): CommandExecutor {
/**
* Responds with a list of running generation tasks.
*/
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
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.name}, Progress ${task.generationTask.count}")
}
response.color(ChatColor.GREEN)
sender.spigot().sendMessage(*response.create())
return true
}
}

@ -1,22 +0,0 @@
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 {
/**
* Stops the specified generation task and removes it from the autostart.
*/
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
return if (command.testPermission(sender) && args.size == 1) {
chunkmaster.generationManager.removeTask(args[0].toInt())
sender.sendMessage("Task ${args[1].toInt()} canceled.")
true
} else {
sender.sendMessage("Invalid argument.")
false
}
}
}

@ -12,14 +12,15 @@ import java.lang.NullPointerException
class GenerationManager(private val chunkmaster: Chunkmaster, private val server: Server) { class GenerationManager(private val chunkmaster: Chunkmaster, private val server: Server) {
val tasks: HashSet<TaskEntry> = HashSet() val tasks: HashSet<TaskEntry> = HashSet()
var paused = false
private set
/** /**
* Adds a generation task * Adds a generation task
*/ */
fun addTask(world: World, stopAfter: Int = -1): Int { fun addTask(world: World, stopAfter: Int = -1): Int {
val centerChunk = world.getChunkAt(world.spawnLocation) val centerChunk = world.getChunkAt(world.spawnLocation)
val generationTask = GenerationTask(chunkmaster, world, centerChunk, centerChunk) val generationTask = GenerationTask(chunkmaster, world, centerChunk, centerChunk, stopAfter)
val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, 2)
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 (?, ?, ?, ?, ?, ?)
@ -31,6 +32,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
insertStatement.setString(5, world.name) insertStatement.setString(5, world.name)
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())
@ -38,9 +40,16 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
val result = getIdStatement.resultSet val result = getIdStatement.resultSet
result.next() result.next()
val id: Int = result.getInt("id") val id: Int = result.getInt("id")
tasks.add(TaskEntry(id, task, generationTask))
insertStatement.close() insertStatement.close()
getIdStatement.close() getIdStatement.close()
if (!paused) {
val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10,
chunkmaster.config.getLong("generation.period"))
tasks.add(TaskEntry(id, task, generationTask))
}
return id return id
} }
@ -48,30 +57,35 @@ 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: Chunk, last: Chunk, id: Int, stopAfter: Int = -1) {
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 = GenerationTask(chunkmaster, world, center, last, stopAfter) val generationTask = GenerationTask(chunkmaster, world, center, last, stopAfter)
val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10, 2) val task = server.scheduler.runTaskTimer(chunkmaster, generationTask, 10,
chunkmaster.config.getLong("generation.period"))
tasks.add(TaskEntry(id, task, generationTask)) tasks.add(TaskEntry(id, task, generationTask))
} }
}
/** /**
* Stops a running generation task. * Stops a running generation task.
*/ */
fun removeTask(id: Int) { fun removeTask(id: Int): Boolean {
val taskEntry = this.tasks.find {it.id == id} val taskEntry = this.tasks.find {it.id == id}
if (taskEntry != null) { if (taskEntry != null) {
taskEntry.generationTask.cancel() taskEntry.generationTask.cancel()
taskEntry.task.cancel() taskEntry.task.cancel()
val deleteTask = chunkmaster.sqliteConnection.prepareStatement("""
DELETE FROM generation_tasks WHERE id = ?;
""".trimIndent())
deleteTask.setInt(1, taskEntry.id)
deleteTask.execute()
deleteTask.close()
if (taskEntry.task.isCancelled) { if (taskEntry.task.isCancelled) {
tasks.remove(taskEntry) tasks.remove(taskEntry)
} }
val setAutostart = chunkmaster.sqliteConnection.prepareStatement(""" return true
DELETE FROM generation_tasks WHERE id = ?
""".trimIndent())
setAutostart.setInt(1, id)
setAutostart.execute()
setAutostart.close()
} }
return false
} }
/** /**
@ -80,6 +94,9 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
*/ */
fun init() { fun init() {
chunkmaster.logger.info("Creating task to load chunk generation Tasks later...") chunkmaster.logger.info("Creating task to load chunk generation Tasks later...")
server.scheduler.runTaskTimer(chunkmaster, Runnable {
saveProgress() // save progress every 30 seconds
}, 600, 600)
server.scheduler.runTaskLater(chunkmaster, Runnable { server.scheduler.runTaskLater(chunkmaster, Runnable {
if (server.onlinePlayers.isEmpty()) { if (server.onlinePlayers.isEmpty()) {
startAll() // run startAll after 10 seconds if empty startAll() // run startAll after 10 seconds if empty
@ -92,14 +109,16 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
*/ */
fun stopAll() { fun stopAll() {
saveProgress() saveProgress()
val removalSet = HashSet<TaskEntry>()
for (task in tasks) { for (task in tasks) {
task.generationTask.cancel() task.generationTask.cancel()
task.task.cancel() task.task.cancel()
if (task.task.isCancelled) { if (task.task.isCancelled) {
tasks.remove(task) removalSet.add(task)
} }
chunkmaster.logger.info("Canceled task #${task.id}") chunkmaster.logger.info("Canceled task #${task.id}")
} }
tasks.removeAll(removalSet)
} }
/** /**
@ -127,12 +146,25 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
} }
} }
savedTasksStatement.close() savedTasksStatement.close()
server.scheduler.runTaskTimer(chunkmaster, Runnable {
saveProgress() // save progress every 30 seconds
}, 600, 600)
chunkmaster.logger.info("${tasks.size} saved tasks loaded.") chunkmaster.logger.info("${tasks.size} saved tasks loaded.")
} }
/**
* Pauses all tasks
*/
fun pauseAll() {
paused = true
stopAll()
}
/**
* Resumes all tasks
*/
fun resumeAll() {
paused = false
startAll()
}
/** /**
* Saves the task progress * Saves the task progress
*/ */

@ -8,9 +8,11 @@ import org.bukkit.scheduler.BukkitTask
class GenerationTask(private val plugin: Chunkmaster, val world: World, class GenerationTask(private val plugin: Chunkmaster, val world: World,
private val centerChunk: Chunk, private val startChunk: Chunk, private val centerChunk: Chunk, private val startChunk: Chunk,
val stopAfter: Int = -1): Runnable { private val stopAfter: Int = -1): Runnable {
private val spiral: Spiral = Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z)) private val spiral: Spiral = Spiral(Pair(centerChunk.x, centerChunk.z), Pair(startChunk.x, startChunk.z))
private val loadedChunks: HashSet<Chunk> = HashSet() private val loadedChunks: HashSet<Chunk> = HashSet()
private val chunkSkips = plugin.config.getInt("generation.chunks-skips-per-step")
private val msptThreshold = plugin.config.getLong("generation.mspt-pause-threshold")
var count = 0 var count = 0
private set private set
@ -25,7 +27,7 @@ class GenerationTask(private val plugin: Chunkmaster, val world: World,
* After 10 chunks have been generated, they will all be unloaded and saved. * After 10 chunks have been generated, they will all be unloaded and saved.
*/ */
override fun run() { override fun run() {
if (plugin.mspt < 500L) { // pause when tps < 2 if (plugin.mspt < msptThreshold) { // pause when tps < 2
if (loadedChunks.size > 10) { if (loadedChunks.size > 10) {
for (chunk in loadedChunks) { for (chunk in loadedChunks) {
if (chunk.isLoaded) { if (chunk.isLoaded) {
@ -33,13 +35,21 @@ class GenerationTask(private val plugin: Chunkmaster, val world: World,
} }
} }
} else { } else {
val nextChunkCoords = spiral.next() if (!world.worldBorder.isInside(lastChunk.getBlock(8, 0, 8).location) || (stopAfter in 1..count)) {
val chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second)
if (!world.worldBorder.isInside(chunk.getBlock(8, 0, 8).location) || (stopAfter in 1..count)) {
endReached = true endReached = true
return return
} }
var nextChunkCoords = spiral.next()
var chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second)
for (i in 1 until chunkSkips) {
if (world.isChunkGenerated(chunk.x, chunk.z)) {
nextChunkCoords = spiral.next() // if the chunk is generated skip 10 chunks per step
chunk = world.getChunkAt(nextChunkCoords.first, nextChunkCoords.second)
} else {
break
}
}
if (!world.isChunkGenerated(chunk.x, chunk.z)) { if (!world.isChunkGenerated(chunk.x, chunk.z)) {
chunk.load(true) chunk.load(true)

@ -3,8 +3,8 @@ package net.trivernis.chunkmaster.lib
import kotlin.math.abs import kotlin.math.abs
class Spiral(private val center: Pair<Int, Int>, start: Pair<Int, Int>) { class Spiral(private val center: Pair<Int, Int>, start: Pair<Int, Int>) {
var currentPos = start private var currentPos = start
var direction = 0 private var direction = 0
var count = 0 var count = 0
/** /**

@ -0,0 +1,8 @@
package net.trivernis.chunkmaster.lib
import org.bukkit.command.CommandSender
interface Subcommand {
val name: String
fun execute(sender: CommandSender, args: List<String>): Boolean
}

@ -6,31 +6,35 @@ author: Trivernis
website: trivernis.net website: trivernis.net
api-version: '1.14' api-version: '1.14'
commands: commands:
generate: chunkmaster:
description: > description: Main command
Generates chunks starting from the world spawnpoint. permission: chunkmaster.chunkmaster
StopAfter indicates the number of chunks to generate usage: /chunkmaster <generate|list|pause|resume|cancel|reload>
(-1 meaning to stop at the world border). If a player aliases:
executes the command, the world argument will be ignored. - chm
permission: chumkmaster.generate - chunkm
usage: /generate [world] [stopAfter] - cmaster
listgentasks:
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: permissions:
cunkmaster.generate: cunkmaster.generate:
description: Allows generate command. description: Allows the generate subcommand.
default: op default: op
chunkmaster.listgentasks: chunkmaster.list:
description: Allows the listgentask command. description: Allows the list subcommand.
default: op default: op
chunkmaster.removegentask: chunkmaster.cancel:
description: Allows the removegentask command. description: Allows the remove subcommand.
default: op
chunkmaster.pause:
description: Allows the pause subcommand.
default: op
chunkmaster.resume:
description: Allows the resume subcommand.
default: op
chunkmaster.reload:
description: Allows the reload subcommand.
default: op
chunkmaster.chunkmaster:
description: Allows Chunkmaster commands.
default: op default: op
chunkmaster.*: chunkmaster.*:
description: Wildcard permission description: Wildcard permission
@ -39,3 +43,6 @@ permissions:
- chunkmaster.generate - chunkmaster.generate
- chunkmaster.listgentasks - chunkmaster.listgentasks
- chunkmaster.removegentask - chunkmaster.removegentask
- chunkmaster.pausegentasks
- chunkmaster.resumegentasks
- chunkmaster.chunkmaster
Loading…
Cancel
Save