Merge pull request #15 from Trivernis/develop

Fixes and changes to the sql structure
pull/23/head v0.13-beta
Trivernis 5 years ago committed by GitHub
commit f1ad4473de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,45 +1,45 @@
# Java Gradle CircleCI 2.0 configuration file # Java Gradle CircleCI 2.0 configuration file
# #
# Check https://circleci.com/docs/2.0/language-java/ for more details # Check https://circleci.com/docs/2.0/language-java/ for more details
# #
version: 2 version: 2
jobs: jobs:
build: build:
docker: docker:
# specify the version you desire here # specify the version you desire here
- image: circleci/openjdk:8-jdk - image: circleci/openjdk:8-jdk
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/ # documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/postgres:9.4 # - image: circleci/postgres:9.4
working_directory: ~/repo working_directory: ~/repo
environment: environment:
# Customize the JVM maximum heap limit # Customize the JVM maximum heap limit
JVM_OPTS: -Xmx3200m JVM_OPTS: -Xmx3200m
TERM: dumb TERM: dumb
steps: steps:
- checkout - checkout
# Download and cache dependencies # Download and cache dependencies
- restore_cache: - restore_cache:
keys: keys:
- v1-dependencies-{{ checksum "build.gradle" }} - v1-dependencies-{{ checksum "build.gradle" }}
# fallback to using the latest cache if no exact match is found # fallback to using the latest cache if no exact match is found
- v1-dependencies- - v1-dependencies-
- run: gradle dependencies - run: gradle dependencies
- save_cache: - save_cache:
paths: paths:
- ~/.gradle - ~/.gradle
key: v1-dependencies-{{ checksum "build.gradle" }} key: v1-dependencies-{{ checksum "build.gradle" }}
# Build jar # Build jar
- run: gradle shadowJar - run: gradle shadowJar
- store_artifacts: - store_artifacts:
path: build/libs path: build/libs
destination: build destination: build

10
.gitignore vendored

@ -1,6 +1,6 @@
gradle gradle
.gradle .gradle
.idea .idea
build build
out out
gradlew* gradlew*

1348
LICENSE

File diff suppressed because it is too large Load Diff

@ -1,75 +1,80 @@
# chunkmaster [![CircleCI](https://circleci.com/gh/Trivernis/spigot-chunkmaster.svg?style=svg)](https://circleci.com/gh/Trivernis/spigot-chunkmaster) # chunkmaster
This plugin can be used to pre-generate the region of a world around the spawn chunk. This plugin can be used to pre-generate the region of a world around the spawn chunk(s).
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 (configurable). 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] [unit]` 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. - `/chunkmaster tpchunk <X> <Z>` Teleports you to the specified chunk coordinates.
## Config ## Config
```yaml ```yaml
generation: generation:
# The maximum amount of chunks that are loaded before unloading and saving them. # If set to true the plugin ignores the vanilla world border and doesn't stop
# Higher values mean higher generation speed but greater memory usage. # the chunk generation when reaching it.
# The value should be a positive integer. # The value should be a boolean <true/false>
max-loaded-chunks: 10 ignore-worldborder: false
# Paper Only # The maximum amount of chunks that are loaded before unloading and saving them.
# The maximum amount of requested chunks with the asynchronous paper chunk # Higher values mean higher generation speed but greater memory usage.
# loading method. Higher values mean faster generation but more memory usage # The value should be a positive integer.
# (and probably bigger performance impact). max-loaded-chunks: 10
# The value should be a positive integer.
max-pending-chunks: 10 # Paper Only
# The maximum amount of requested chunks with the asynchronous paper chunk
# The period (in ticks) in which a generation step is run. # loading method. Higher values mean faster generation but more memory usage
# Higher values mean less performance impact but slower generation. # (and probably bigger performance impact).
# The value should be a positive integer. # The value should be a positive integer.
period: 2 max-pending-chunks: 10
# The max amount of chunks that should be generated per step. # The period (in ticks) in which a generation step is run.
# Higher values mean higher generation speed but higher performance impact. # Higher values mean less performance impact but slower generation.
# The value should be a positive integer. # The value should be a positive integer.
chunks-per-step: 4 period: 2
# Paper Only # The max amount of chunks that should be generated per step.
# The number of already generated chunks that will be skipped for each step. # Higher values mean higher generation speed but higher performance impact.
# Notice that these still have a performance impact because the server needs to check # The value should be a positive integer.
# if the chunk is generated. chunks-per-step: 4
# Higher values mean faster generation but greater performance impact.
# The value should be a positive integer. # Paper Only
chunk-skips-per-step: 100 # 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
# The maximum milliseconds per tick the server is allowed to have # if the chunk is generated.
# during the cunk generation process. # Higher values mean faster generation but greater performance impact.
# If the mspt is greather than this, the chunk generation task pauses. # The value should be a positive integer.
# The value should be a positive integer greater than 50. chunk-skips-per-step: 100
mspt-pause-threshold: 500
# The maximum milliseconds per tick the server is allowed to have
# If the chunk generation process should pause on player join. # during the cunk generation process.
# Notice that playing on a server that constantly generates chunks can be # If the mspt is greather than this, the chunk generation task pauses.
# very laggy and can cause it to crash. # The value should be a positive integer greater than 50.
# You could configure the values above so that the performance impact of the generation mspt-pause-threshold: 500
# process is minimal.
# The value should be a boolean <true/false> # If the chunk generation process should pause on player join.
pause-on-join: true # 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
## Spigot and Paper # process is minimal.
# The value should be a boolean <true/false>
The plugin works on spigot and paper servers but is significantly faster on paper servers pause-on-join: true
(because it profits from asynchronous chunk loading an the better implementation of the ```
## Spigot and Paper
The plugin works on spigot and paper servers but is significantly faster on paper servers
(because it profits from asynchronous chunk loading an the better implementation of the
isChunkGenerated method). isChunkGenerated method).

@ -1,79 +1,80 @@
buildscript { buildscript {
repositories { repositories {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath "com.github.jengelman.gradle.plugins:shadow:2.0.2" classpath "com.github.jengelman.gradle.plugins:shadow:2.0.2"
} }
} }
plugins { plugins {
id 'idea' id 'idea'
id 'org.jetbrains.kotlin.jvm' version '1.3.50' id 'org.jetbrains.kotlin.jvm' version '1.3.50'
id 'com.github.johnrengelman.shadow' version '2.0.4' id 'com.github.johnrengelman.shadow' version '2.0.4'
} }
idea { idea {
module { module {
downloadJavadoc = true downloadJavadoc = true
downloadSources = true downloadSources = true
} }
} }
group "net.trivernis" group "net.trivernis"
version "0.12-beta" version "0.13-beta"
sourceCompatibility = 1.8 sourceCompatibility = 1.8
repositories { repositories {
mavenCentral() mavenCentral()
maven { maven {
url "https://hub.spigotmc.org/nexus/content/repositories/snapshots" url "https://hub.spigotmc.org/nexus/content/repositories/snapshots"
} }
maven { maven {
url 'https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc' url 'https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc'
} }
maven { maven {
name 'papermc' name 'papermc'
url 'https://papermc.io/repo/repository/maven-public/' url 'https://papermc.io/repo/repository/maven-public/'
} }
maven { maven {
name 'CodeMc' name 'CodeMc'
url 'https://repo.codemc.org/repository/maven-public' url 'https://repo.codemc.org/repository/maven-public'
} }
} }
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'junit', name: 'junit', version: '4.12'
compileOnly "org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT" //compileOnly "org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT"
compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' compileOnly "com.destroystokyo.paper:paper-api:1.14.4-R0.1-SNAPSHOT"
compile "io.papermc:paperlib:1.0.2" compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0'
compile "org.bstats:bstats-bukkit:1.5" compile "io.papermc:paperlib:1.0.2"
} compile "org.bstats:bstats-bukkit:1.5"
}
apply plugin: "com.github.johnrengelman.shadow"
apply plugin: 'java' apply plugin: "com.github.johnrengelman.shadow"
apply plugin: 'java'
shadowJar {
relocate 'io.papermc.lib', 'net.trivernis.chunkmaster.paperlib' shadowJar {
relocate 'org.bstats', 'net.trivernis.chunkmaster.bstats' relocate 'io.papermc.lib', 'net.trivernis.chunkmaster.paperlib'
} relocate 'org.bstats', 'net.trivernis.chunkmaster.bstats'
}
jar {
from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } jar {
} from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
compileKotlin {
kotlinOptions { compileKotlin {
jvmTarget = "1.8" kotlinOptions {
} jvmTarget = "1.8"
} }
}
compileTestKotlin {
kotlinOptions { compileTestKotlin {
jvmTarget = "1.8" kotlinOptions {
} jvmTarget = "1.8"
}
} }

@ -1,2 +1,2 @@
rootProject.name = 'chunkmaster' rootProject.name = 'chunkmaster'

@ -1,91 +1,90 @@
package net.trivernis.chunkmaster package net.trivernis.chunkmaster
import io.papermc.lib.PaperLib import io.papermc.lib.PaperLib
import net.trivernis.chunkmaster.commands.* import net.trivernis.chunkmaster.commands.*
import net.trivernis.chunkmaster.lib.generation.GenerationManager import net.trivernis.chunkmaster.lib.generation.GenerationManager
import net.trivernis.chunkmaster.lib.SqlUpdateManager import net.trivernis.chunkmaster.lib.SqliteManager
import org.bstats.bukkit.Metrics import org.bstats.bukkit.Metrics
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.DriverManager class Chunkmaster: JavaPlugin() {
lateinit var sqliteManager: SqliteManager
class Chunkmaster: JavaPlugin() { lateinit var generationManager: GenerationManager
lateinit var sqliteConnection: Connection private lateinit var tpsTask: BukkitTask
var dbname: String? = null var mspt = 20 // keep track of the milliseconds per tick
lateinit var generationManager: GenerationManager private set
private lateinit var tpsTask: BukkitTask
var mspt = 50L // keep track of the milliseconds per tick /**
private set * On enable of the plugin
*/
/** override fun onEnable() {
* On enable of the plugin PaperLib.suggestPaper(this)
*/ configure()
override fun onEnable() {
PaperLib.suggestPaper(this) val metrics = Metrics(this)
configure()
initDatabase()
val metrics = Metrics(this) generationManager = GenerationManager(this, server)
generationManager.init()
initDatabase()
generationManager = GenerationManager(this, server) getCommand("chunkmaster")?.aliases = mutableListOf("chm", "chunkm", "cmaster")
generationManager.init() getCommand("chunkmaster")?.setExecutor(CommandChunkmaster(this, server))
getCommand("chunkmaster")?.aliases = mutableListOf("chm", "chunkm", "cmaster") server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this)
getCommand("chunkmaster")?.setExecutor(CommandChunkmaster(this, server))
if (PaperLib.isPaper() && PaperLib.getMinecraftPatchVersion() >= 225) {
server.pluginManager.registerEvents(ChunkmasterEvents(this, server), this) tpsTask = server.scheduler.runTaskTimer(this, Runnable {
mspt = 1000/server.currentTick // use papers exposed tick rather than calculating it
tpsTask = server.scheduler.runTaskTimer(this, Runnable { }, 1, 300)
val start = System.currentTimeMillis() } else {
server.scheduler.runTaskLater(this, Runnable { tpsTask = server.scheduler.runTaskTimer(this, Runnable {
mspt = System.currentTimeMillis() - start val start = System.currentTimeMillis()
}, 1) server.scheduler.runTaskLater(this, Runnable {
}, 1, 300) mspt = (System.currentTimeMillis() - start).toInt()
} }, 1)
}, 1, 300)
/** }
* Stop all tasks and close database connection on disable }
*/
override fun onDisable() { /**
logger.info("Stopping all generation tasks...") * Stop all tasks and close database connection on disable
generationManager.stopAll() */
sqliteConnection.close() override fun onDisable() {
} logger.info("Stopping all generation tasks...")
generationManager.stopAll()
/** }
* Cofigure the config file
*/ /**
private fun configure() { * Cofigure the config file
dataFolder.mkdir() */
config.addDefault("generation.period", 2L) private fun configure() {
config.addDefault("generation.chunks-per-step", 2) dataFolder.mkdir()
config.addDefault("generation.chunk-skips-per-step", 100) config.addDefault("generation.period", 2L)
config.addDefault("generation.mspt-pause-threshold", 500L) config.addDefault("generation.chunks-per-step", 2)
config.addDefault("generation.pause-on-join", true) config.addDefault("generation.chunk-skips-per-step", 100)
config.addDefault("generation.max-pending-chunks", 10) config.addDefault("generation.mspt-pause-threshold", 500L)
config.addDefault("generation.max-loaded-chunks", 10) config.addDefault("generation.pause-on-join", true)
config.options().copyDefaults(true) config.addDefault("generation.max-pending-chunks", 10)
saveConfig() config.addDefault("generation.max-loaded-chunks", 10)
} config.addDefault("generation.ignore-worldborder", false)
config.addDefault("database.filename", "chunkmaster.db")
/** config.options().copyDefaults(true)
* Initializes the database saveConfig()
*/ }
private fun initDatabase() {
logger.info("Initializing Database...") /**
try { * Initializes the database
Class.forName("org.sqlite.JDBC") */
sqliteConnection = DriverManager.getConnection("jdbc:sqlite:${dataFolder.absolutePath}/chunkmaster.db") private fun initDatabase() {
logger.info("Database connection established.") logger.info("Initializing Database...")
try {
val updateManager = SqlUpdateManager(sqliteConnection, this) this.sqliteManager = SqliteManager( this)
updateManager.checkUpdate() sqliteManager.init()
updateManager.performUpdate() logger.info("Database fully initialized.")
logger.info("Database fully initialized.") } catch(e: Exception) {
} catch(e: Exception) { logger.warning("Failed to init database: ${e.message}")
logger.warning("Failed to init database: ${e.message}") }
} }
}
} }

@ -1,68 +1,55 @@
package net.trivernis.chunkmaster package net.trivernis.chunkmaster
import net.trivernis.chunkmaster.lib.generation.GenerationTaskPaper 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 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 {
private val pauseOnJoin = chunkmaster.config.getBoolean("generation.pause-on-join") private val pauseOnJoin = chunkmaster.config.getBoolean("generation.pause-on-join")
private var playerPaused = false private var playerPaused = false
/** /**
* Autostart generation tasks * Autostart generation tasks
*/ */
@EventHandler @EventHandler
fun onPlayerQuit(event: PlayerQuitEvent) { fun onPlayerQuit(event: PlayerQuitEvent) {
if (pauseOnJoin) { 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 (!playerPaused) { if (!playerPaused) {
if (chunkmaster.generationManager.pausedTasks.isNotEmpty()) { 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() 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
} else { } else {
chunkmaster.logger.info("Generation tasks are already running.") chunkmaster.logger.info("Generation tasks are already running.")
} }
} }
} }
} }
/** /**
* Autostop generation tasks * Autostop generation tasks
*/ */
@EventHandler @EventHandler
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()) { if (chunkmaster.generationManager.tasks.isNotEmpty()) {
chunkmaster.logger.info("Pausing generation tasks because of player join.") chunkmaster.logger.info("Pausing generation tasks because of player join.")
} }
playerPaused = chunkmaster.generationManager.paused playerPaused = chunkmaster.generationManager.paused
chunkmaster.generationManager.pauseAll() chunkmaster.generationManager.pauseAll()
} }
} }
} }
/**
* 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()
}
}
}
} }

@ -1,48 +1,48 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand import net.trivernis.chunkmaster.lib.Subcommand
import net.trivernis.chunkmaster.lib.generation.TaskEntry import net.trivernis.chunkmaster.lib.generation.TaskEntry
import org.bukkit.command.Command import org.bukkit.command.Command
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
class CmdCancel(private val chunkmaster: Chunkmaster): Subcommand { class CmdCancel(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "cancel" override val name = "cancel"
/** /**
* TabComplete for subcommand cancel. * TabComplete for subcommand cancel.
*/ */
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
command: Command, command: Command,
alias: String, alias: String,
args: List<String> args: List<String>
): MutableList<String> { ): MutableList<String> {
val genManager = chunkmaster.generationManager val genManager = chunkmaster.generationManager
val allTasks = genManager.allTasks val allTasks = genManager.allTasks
return allTasks.filter {it.id.toString().indexOf(args[0]) == 0} return allTasks.filter {it.id.toString().indexOf(args[0]) == 0}
.map { it.id.toString() }.toMutableList() .map { it.id.toString() }.toMutableList()
} }
/** /**
* Cancels the generation task if it exists. * Cancels the generation task if it exists.
*/ */
override fun execute(sender: CommandSender, args: List<String>): Boolean { override fun execute(sender: CommandSender, args: List<String>): Boolean {
return if (args.isNotEmpty() && args[0].toIntOrNull() != null) { return if (args.isNotEmpty() && args[0].toIntOrNull() != null) {
if (chunkmaster.generationManager.removeTask(args[0].toInt())) { if (chunkmaster.generationManager.removeTask(args[0].toInt())) {
sender.sendMessage("Task ${args[0]} canceled.") sender.sendMessage("Task ${args[0]} canceled.")
true true
} else { } else {
sender.spigot().sendMessage(*ComponentBuilder("Task ${args[0]} not found!") sender.spigot().sendMessage(*ComponentBuilder("Task ${args[0]} not found!")
.color(ChatColor.RED).create()) .color(ChatColor.RED).create())
false false
} }
} else { } else {
sender.spigot().sendMessage(*ComponentBuilder("You need to provide a task id to cancel.") sender.spigot().sendMessage(*ComponentBuilder("You need to provide a task id to cancel.")
.color(ChatColor.RED).create()) .color(ChatColor.RED).create())
false false
} }
} }
} }

@ -1,133 +1,133 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand 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 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"
/** /**
* TabComplete for generate command. * TabComplete for generate command.
*/ */
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
command: Command, command: Command,
alias: String, alias: String,
args: List<String> args: List<String>
): MutableList<String> { ): MutableList<String> {
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) { } else if (args.size == 2) {
if (args[0].toIntOrNull() != null) { if (args[0].toIntOrNull() != null) {
return units.filter {it.indexOf(args[1]) == 0}.toMutableList() return units.filter {it.indexOf(args[1]) == 0}.toMutableList()
} }
} else if (args.size > 2) { } else if (args.size > 2) {
if (args[1].toIntOrNull() != null) { if (args[1].toIntOrNull() != null) {
return units.filter {it.indexOf(args[2]) == 0}.toMutableList() return units.filter {it.indexOf(args[2]) == 0}.toMutableList()
} }
} }
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
} }
val units = listOf("blockradius", "radius", "diameter") 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.
*/ */
override fun execute(sender: CommandSender, args: List<String>): Boolean { override fun execute(sender: CommandSender, args: List<String>): Boolean {
var worldName = "" var worldName = ""
var stopAfter = -1 var stopAfter = -1
if (sender is Player) { if (sender is Player) {
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 worldName = sender.world.name
} else { } else {
worldName = args[0] worldName = args[0]
} }
if (args.size > 1) { if (args.size > 1) {
if (args[1].toIntOrNull() != null) { if (args[1].toIntOrNull() != null) {
stopAfter = args[1].toInt() stopAfter = args[1].toInt()
} else if (args[1] in units && args[0].toIntOrNull() != null) { } else if (args[1] in units && args[0].toIntOrNull() != null) {
stopAfter = getStopAfter(stopAfter, args[1]) stopAfter = getStopAfter(stopAfter, args[1])
} else { } else {
worldName = args[1] worldName = args[1]
} }
} }
if (args.size > 2 && args[2] in units && args[1].toIntOrNull() != null) { if (args.size > 2 && args[2] in units && args[1].toIntOrNull() != null) {
stopAfter = getStopAfter(stopAfter, args[2]) stopAfter = getStopAfter(stopAfter, args[2])
} }
} else { } else {
worldName = sender.world.name worldName = sender.world.name
} }
} else { } else {
if (args.isNotEmpty()) { if (args.isNotEmpty()) {
worldName = args[0] worldName = args[0]
if (args.size > 1) { if (args.size > 1) {
if (args[1].toIntOrNull() != null) { if (args[1].toIntOrNull() != null) {
stopAfter = args[1].toInt() stopAfter = args[1].toInt()
} }
} }
if (args.size > 2 && args[2] in units) { if (args.size > 2 && args[2] in units) {
stopAfter = getStopAfter(stopAfter, args[2]) 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) return createTask(sender, worldName, stopAfter)
} }
/** /**
* Returns stopAfter for a given unit * Returns stopAfter for a given unit
*/ */
private fun getStopAfter(number: Int, unit: String): Int { private fun getStopAfter(number: Int, unit: String): Int {
if (unit in units) { if (unit in units) {
return when (unit) { return when (unit) {
"radius" -> { "radius" -> {
((number * 2)+1).toDouble().pow(2.0).toInt() ((number * 2)+1).toDouble().pow(2.0).toInt()
} }
"diameter" -> { "diameter" -> {
number.toDouble().pow(2.0).toInt() number.toDouble().pow(2.0).toInt()
} }
"blockradius" -> { "blockradius" -> {
((number.toDouble()+1)/8).pow(2.0).toInt() ((number.toDouble()+1)/8).pow(2.0).toInt()
} }
else -> number else -> number
} }
} }
return number return number
} }
/** /**
* Creates the task with the given arguments. * Creates the task with the given arguments.
*/ */
private fun createTask(sender: CommandSender, worldName: String, stopAfter: Int): Boolean { 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) {
chunkmaster.generationManager.addTask(world, stopAfter) chunkmaster.generationManager.addTask(world, stopAfter)
sender.spigot().sendMessage(*ComponentBuilder("Generation task for world ").color(ChatColor.BLUE) sender.spigot().sendMessage(*ComponentBuilder("Generation task for world ").color(ChatColor.BLUE)
.append(worldName).color(ChatColor.GREEN).append(" until ").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(if (stopAfter > 0) "$stopAfter chunks" else "WorldBorder").color(ChatColor.GREEN)
.append(" successfully created").color(ChatColor.BLUE).create()) .append(" successfully created").color(ChatColor.BLUE).create())
true true
} else if (world == null){ } else if (world == null){
sender.spigot().sendMessage(*ComponentBuilder("World ").color(ChatColor.RED) sender.spigot().sendMessage(*ComponentBuilder("World ").color(ChatColor.RED)
.append(worldName).color(ChatColor.GREEN).append(" not found!").color(ChatColor.RED).create()) .append(worldName).color(ChatColor.GREEN).append(" not found!").color(ChatColor.RED).create())
false false
} else { } else {
sender.spigot().sendMessage(*ComponentBuilder("Task already exists!").color(ChatColor.RED).create()) sender.spigot().sendMessage(*ComponentBuilder("Task already exists!").color(ChatColor.RED).create())
return false return false
} }
} }
} }

@ -1,63 +1,63 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand 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
class CmdList(private val chunkmaster: Chunkmaster): Subcommand { class CmdList(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "list" override val name = "list"
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
command: Command, command: Command,
alias: String, alias: String,
args: List<String> args: List<String>
): MutableList<String> { ): MutableList<String> {
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
} }
/** /**
* Lists all running or paused generation tasks. * Lists all running or paused generation tasks.
*/ */
override fun execute(sender: CommandSender, args: List<String>): Boolean { override fun execute(sender: CommandSender, args: List<String>): Boolean {
val runningTasks = chunkmaster.generationManager.tasks val runningTasks = chunkmaster.generationManager.tasks
val pausedTasks = chunkmaster.generationManager.pausedTasks val pausedTasks = chunkmaster.generationManager.pausedTasks
if (runningTasks.isEmpty() && pausedTasks.isEmpty()) { if (runningTasks.isEmpty() && pausedTasks.isEmpty()) {
sender.spigot().sendMessage(*ComponentBuilder("There are no generation tasks.") sender.spigot().sendMessage(*ComponentBuilder("There are no generation tasks.")
.color(ChatColor.BLUE).create()) .color(ChatColor.BLUE).create())
} else if (runningTasks.isEmpty()) { } else if (runningTasks.isEmpty()) {
val response = ComponentBuilder("Currently paused generation tasks:").color(ChatColor.WHITE).bold(true) val response = ComponentBuilder("Currently paused generation tasks:").color(ChatColor.WHITE).bold(true)
for (taskEntry in pausedTasks) { for (taskEntry in pausedTasks) {
val genTask = taskEntry.generationTask val genTask = taskEntry.generationTask
response.append("\n - ").color(ChatColor.WHITE).bold(false) response.append("\n - ").color(ChatColor.WHITE).bold(false)
response.append("#${taskEntry.id}").color(ChatColor.BLUE).append(" - ").color(ChatColor.WHITE) response.append("#${taskEntry.id}").color(ChatColor.BLUE).append(" - ").color(ChatColor.WHITE)
response.append(genTask.world.name).color(ChatColor.GREEN).append(" - Progress: ").color(ChatColor.WHITE) response.append(genTask.world.name).color(ChatColor.GREEN).append(" - Progress: ").color(ChatColor.WHITE)
response.append("${genTask.count} chunks").color(ChatColor.BLUE) response.append("${genTask.count} chunks").color(ChatColor.BLUE)
if (genTask.stopAfter > 0) { if (genTask.stopAfter > 0) {
response.append(" (${(genTask.count.toDouble()/genTask.stopAfter.toDouble())*100}%).") response.append(" (${(genTask.count.toDouble()/genTask.stopAfter.toDouble())*100}%).")
} }
} }
sender.spigot().sendMessage(*response.create()) sender.spigot().sendMessage(*response.create())
} else { } else {
val response = ComponentBuilder("Currently running generation tasks:").color(ChatColor.WHITE).bold(true) val response = ComponentBuilder("Currently running generation tasks:").color(ChatColor.WHITE).bold(true)
for (task in runningTasks) { for (task in runningTasks) {
val genTask = task.generationTask val genTask = task.generationTask
response.append("\n - ").color(ChatColor.WHITE).bold(false) response.append("\n - ").color(ChatColor.WHITE).bold(false)
.append("#${task.id}").color(ChatColor.BLUE).append(" - ").color(ChatColor.WHITE) .append("#${task.id}").color(ChatColor.BLUE).append(" - ").color(ChatColor.WHITE)
.append(genTask.world.name).color(ChatColor.GREEN).append(" - Progress: ").color(ChatColor.WHITE) .append(genTask.world.name).color(ChatColor.GREEN).append(" - Progress: ").color(ChatColor.WHITE)
.append("${genTask.count} chunks").color(ChatColor.BLUE) .append("${genTask.count} chunks").color(ChatColor.BLUE)
if (genTask.stopAfter > 0) { if (genTask.stopAfter > 0) {
response.append(" (${(genTask.count.toDouble()/genTask.stopAfter.toDouble())*100}%).") response.append(" (${(genTask.count.toDouble()/genTask.stopAfter.toDouble())*100}%).")
} }
} }
sender.spigot().sendMessage(*response.create()) sender.spigot().sendMessage(*response.create())
} }
return true return true
} }
} }

@ -1,37 +1,37 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand 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
class CmdPause(private val chunkmaster: Chunkmaster) : Subcommand { class CmdPause(private val chunkmaster: Chunkmaster) : Subcommand {
override val name: String = "pause" override val name: String = "pause"
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
command: Command, command: Command,
alias: String, alias: String,
args: List<String> args: List<String>
): MutableList<String> { ): MutableList<String> {
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
} }
override fun execute(sender: CommandSender, args: List<String>): Boolean { override fun execute(sender: CommandSender, args: List<String>): Boolean {
return if (!chunkmaster.generationManager.paused) { return if (!chunkmaster.generationManager.paused) {
chunkmaster.generationManager.pauseAll() chunkmaster.generationManager.pauseAll()
sender.spigot().sendMessage( sender.spigot().sendMessage(
*ComponentBuilder("Paused all generation tasks.") *ComponentBuilder("Paused all generation tasks.")
.color(ChatColor.BLUE).create() .color(ChatColor.BLUE).create()
) )
true true
} else { } else {
sender.spigot().sendMessage( sender.spigot().sendMessage(
*ComponentBuilder("The generation process is already paused.").color(ChatColor.RED).create() *ComponentBuilder("The generation process is already paused.").color(ChatColor.RED).create()
) )
false false
} }
} }
} }

@ -1,34 +1,34 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand 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
class CmdReload(private val chunkmaster: Chunkmaster): Subcommand { class CmdReload(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "reload" override val name = "reload"
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
command: Command, command: Command,
alias: String, alias: String,
args: List<String> args: List<String>
): MutableList<String> { ): MutableList<String> {
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
} }
/** /**
* Reload command to reload the config and restart the tasks. * Reload command to reload the config and restart the tasks.
*/ */
override fun execute(sender: CommandSender, args: List<String>): Boolean { override fun execute(sender: CommandSender, args: List<String>): Boolean {
sender.spigot().sendMessage(*ComponentBuilder("Reloading config and restarting tasks...") sender.spigot().sendMessage(*ComponentBuilder("Reloading config and restarting tasks...")
.color(ChatColor.YELLOW).create()) .color(ChatColor.YELLOW).create())
chunkmaster.generationManager.stopAll() chunkmaster.generationManager.stopAll()
chunkmaster.reloadConfig() chunkmaster.reloadConfig()
chunkmaster.generationManager.startAll() chunkmaster.generationManager.startAll()
sender.spigot().sendMessage(*ComponentBuilder("Config reload complete!").color(ChatColor.GREEN).create()) sender.spigot().sendMessage(*ComponentBuilder("Config reload complete!").color(ChatColor.GREEN).create())
return true return true
} }
} }

@ -1,34 +1,34 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand 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
class CmdResume(private val chunkmaster: Chunkmaster): Subcommand { class CmdResume(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "resume" override val name = "resume"
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
command: Command, command: Command,
alias: String, alias: String,
args: List<String> args: List<String>
): MutableList<String> { ): MutableList<String> {
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
} }
override fun execute(sender: CommandSender, args: List<String>): Boolean { override fun execute(sender: CommandSender, args: List<String>): Boolean {
return if (chunkmaster.generationManager.paused) { return if (chunkmaster.generationManager.paused) {
chunkmaster.generationManager.resumeAll() chunkmaster.generationManager.resumeAll()
sender.spigot().sendMessage( sender.spigot().sendMessage(
*ComponentBuilder("Resumed all generation tasks.").color(ChatColor.BLUE).create()) *ComponentBuilder("Resumed all generation tasks.").color(ChatColor.BLUE).create())
true true
} else { } else {
sender.spigot().sendMessage( sender.spigot().sendMessage(
*ComponentBuilder("There are no paused generation tasks.").color(ChatColor.RED).create()) *ComponentBuilder("There are no paused generation tasks.").color(ChatColor.RED).create())
false false
} }
} }
} }

@ -1,52 +1,52 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import io.papermc.lib.PaperLib import io.papermc.lib.PaperLib
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.lib.Subcommand import net.trivernis.chunkmaster.lib.Subcommand
import org.bukkit.Material import org.bukkit.Material
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
class CmdTpChunk: Subcommand { class CmdTpChunk: Subcommand {
override val name = "tpchunk" override val name = "tpchunk"
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
command: Command, command: Command,
alias: String, alias: String,
args: List<String> args: List<String>
): MutableList<String> { ): MutableList<String> {
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
} }
/** /**
* Teleports the player to a save location in the chunk * Teleports the player to a save location in the chunk
*/ */
override fun execute(sender: CommandSender, args: List<String>): Boolean { override fun execute(sender: CommandSender, args: List<String>): Boolean {
if (sender is Player) { if (sender is Player) {
if (args.size == 2 && args[0].toIntOrNull() != null && args[1].toIntOrNull() != null) { 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 val location = sender.world.getChunkAt(args[0].toInt(), args[1].toInt()).getBlock(8, 60, 8).location
while (location.block.blockData.material != Material.AIR) { while (location.block.blockData.material != Material.AIR) {
location.y++ location.y++
} }
if (PaperLib.isPaper()) { if (PaperLib.isPaper()) {
PaperLib.teleportAsync(sender, location) PaperLib.teleportAsync(sender, location)
} else { } else {
sender.teleport(location) sender.teleport(location)
} }
sender.spigot().sendMessage(*ComponentBuilder("You have been teleportet to chunk") sender.spigot().sendMessage(*ComponentBuilder("You have been teleportet to chunk")
.color(ChatColor.YELLOW).append("${args[0]}, ${args[1]}").color(ChatColor.BLUE).create()) .color(ChatColor.YELLOW).append("${args[0]}, ${args[1]}").color(ChatColor.BLUE).create())
return true return true
} else { } else {
return false return false
} }
} else { } else {
sender.spigot().sendMessage(*ComponentBuilder("This command can only be executed by a player!") sender.spigot().sendMessage(*ComponentBuilder("This command can only be executed by a player!")
.color(ChatColor.RED).create()) .color(ChatColor.RED).create())
return false return false
} }
} }
} }

@ -1,90 +1,90 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand import net.trivernis.chunkmaster.lib.Subcommand
import org.bukkit.Server import org.bukkit.Server
import org.bukkit.command.Command import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor 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>()
init { init {
registerCommands() registerCommands()
} }
/** /**
* Tab complete for commands * Tab complete for commands
*/ */
override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array<out String>): override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array<out String>):
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]]
return commandEntry!!.onTabComplete(sender, command, alias, args.slice(1 until args.size)) return commandEntry!!.onTabComplete(sender, command, alias, args.slice(1 until args.size))
} }
} }
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
} }
/** /**
* /chunkmaster command to handle all commands * /chunkmaster command to handle all commands
*/ */
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()) {
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( sender.spigot().sendMessage(
*ComponentBuilder("Subcommand ").color(ChatColor.RED) *ComponentBuilder("Subcommand ").color(ChatColor.RED)
.append(args[0]).color(ChatColor.GREEN).append(" not found").color(ChatColor.RED).create() .append(args[0]).color(ChatColor.GREEN).append(" not found").color(ChatColor.RED).create()
) )
false false
} }
} else { } else {
sender.spigot().sendMessage( sender.spigot().sendMessage(
*ComponentBuilder("You do not have permission!") *ComponentBuilder("You do not have permission!")
.color(ChatColor.RED).create() .color(ChatColor.RED).create()
) )
} }
return true return true
} else { } else {
return false return false
} }
} }
/** /**
* Registers all subcommands. * Registers all subcommands.
*/ */
private fun registerCommands() { private fun registerCommands() {
val cmdGenerate = CmdGenerate(chunkmaster) val cmdGenerate = CmdGenerate(chunkmaster)
commands[cmdGenerate.name] = cmdGenerate commands[cmdGenerate.name] = cmdGenerate
val cmdPause = CmdPause(chunkmaster) val cmdPause = CmdPause(chunkmaster)
commands[cmdPause.name] = cmdPause commands[cmdPause.name] = cmdPause
val cmdResume = CmdResume(chunkmaster) val cmdResume = CmdResume(chunkmaster)
commands[cmdResume.name] = cmdResume commands[cmdResume.name] = cmdResume
val cmdCancel = CmdCancel(chunkmaster) val cmdCancel = CmdCancel(chunkmaster)
commands[cmdCancel.name] = cmdCancel commands[cmdCancel.name] = cmdCancel
val cmdList = CmdList(chunkmaster) val cmdList = CmdList(chunkmaster)
commands[cmdList.name] = cmdList commands[cmdList.name] = cmdList
val cmdReload = CmdReload(chunkmaster) val cmdReload = CmdReload(chunkmaster)
commands[cmdReload.name] = cmdReload commands[cmdReload.name] = cmdReload
val cmdTpChunk = CmdTpChunk() val cmdTpChunk = CmdTpChunk()
commands[cmdTpChunk.name] = cmdTpChunk commands[cmdTpChunk.name] = cmdTpChunk
} }
} }

@ -1,64 +1,64 @@
package net.trivernis.chunkmaster.lib 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>) {
private var currentPos = start private var currentPos = start
private var direction = 0 private var direction = 0
var count = 0 var count = 0
/** /**
* Returns the next value in the spiral * Returns the next value in the spiral
*/ */
fun next(): Pair<Int, Int> { fun next(): Pair<Int, Int> {
if (count == 0 && currentPos != center) { if (count == 0 && currentPos != center) {
// simulate the spiral to get the correct direction // simulate the spiral to get the correct direction
// TODO: Improve performance of this workaround (replace it with acutal stuff) // TODO: Improve performance of this workaround (replace it with acutal stuff)
val simSpiral = Spiral(center, center) val simSpiral = Spiral(center, center)
while (simSpiral.next() != currentPos); while (simSpiral.next() != currentPos);
direction = simSpiral.direction direction = simSpiral.direction
count = simSpiral.count count = simSpiral.count
} }
if (count == 1) { // because of the center behaviour if (count == 1) { // because of the center behaviour
count ++ count ++
return currentPos return currentPos
} }
if (currentPos == center) { // the center has to be handled exclusively if (currentPos == center) { // the center has to be handled exclusively
currentPos = Pair(center.first, center.second + 1) currentPos = Pair(center.first, center.second + 1)
count ++ count ++
return center return center
} else { } else {
val distances = getDistances(center, currentPos) val distances = getDistances(center, currentPos)
if (abs(distances.first) == abs(distances.second)) { if (abs(distances.first) == abs(distances.second)) {
direction = (direction + 1)%5 direction = (direction + 1)%5
} }
} }
when(direction) { when(direction) {
0 -> { 0 -> {
currentPos = Pair(currentPos.first + 1, currentPos.second) currentPos = Pair(currentPos.first + 1, currentPos.second)
} }
1 -> { 1 -> {
currentPos = Pair(currentPos.first, currentPos.second - 1) currentPos = Pair(currentPos.first, currentPos.second - 1)
} }
2 -> { 2 -> {
currentPos = Pair(currentPos.first - 1, currentPos.second) currentPos = Pair(currentPos.first - 1, currentPos.second)
} }
3 -> { 3 -> {
currentPos = Pair(currentPos.first, currentPos.second + 1) currentPos = Pair(currentPos.first, currentPos.second + 1)
} }
4 -> { 4 -> {
currentPos = Pair(currentPos.first, currentPos.second + 1) currentPos = Pair(currentPos.first, currentPos.second + 1)
direction = 0 direction = 0
} }
} }
count ++ count ++
return currentPos return currentPos
} }
/** /**
* Returns the distances between 2 coordinates * Returns the distances between 2 coordinates
*/ */
private fun getDistances(pos1: Pair<Int, Int>, pos2: Pair<Int, Int>): Pair<Int, Int> { private fun getDistances(pos1: Pair<Int, Int>, pos2: Pair<Int, Int>): Pair<Int, Int> {
return Pair(pos2.first - pos1.first, pos2.second - pos1.second) return Pair(pos2.first - pos1.first, pos2.second - pos1.second)
} }
} }

@ -1,79 +0,0 @@
package net.trivernis.chunkmaster.lib
import net.trivernis.chunkmaster.Chunkmaster
import java.lang.Exception
import java.sql.Connection
class SqlUpdateManager(private val connnection: Connection, private val chunkmaster: Chunkmaster) {
private val tables = listOf(
Pair(
"generation_tasks",
listOf(
Pair("id", "integer PRIMARY KEY AUTOINCREMENT"),
Pair("center_x", "integer NOT NULL DEFAULT 0"),
Pair("center_z", "integer NOT NULL DEFAULT 0"),
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")
)
)
)
private val needUpdate = HashSet<Pair<String, Pair<String, String>>>()
private val needCreation = HashSet<String>()
/**
* Checks which tables need an update or creation.
*/
fun checkUpdate() {
val meta = connnection.metaData
for (table in tables) {
val resTables = meta.getTables(null, null, table.first, null)
if (resTables.next()) { // table exists
for (column in table.second) {
val resColumn = meta.getColumns(null, null, table.first, column.first)
if (!resColumn.next()) {
needUpdate.add(Pair(table.first, column))
}
}
} else {
needCreation.add(table.first)
}
}
}
/**
* Creates or updates tables that needed an update.
*/
fun performUpdate() {
for (table in needCreation) {
try {
var tableDef = "CREATE TABLE IF NOT EXISTS $table ("
for (column in tables.find{it.first == table}!!.second) {
tableDef += "${column.first} ${column.second},"
}
tableDef = tableDef.substringBeforeLast(",") + ");"
chunkmaster.logger.info("Creating table $table with definition $tableDef")
val stmt = connnection.prepareStatement(tableDef)
stmt.execute()
stmt.close()
} catch (err: Exception) {
chunkmaster.logger.severe("Error creating table $table.")
}
}
for (table in needUpdate) {
val updateSql = "ALTER TABLE ${table.first} ADD COLUMN ${table.second.first} ${table.second.second}"
try {
val stmt = connnection.prepareStatement(updateSql)
stmt.execute()
stmt.close()
chunkmaster.logger.info("Updated table ${table.first} with sql $updateSql")
} catch (e: Exception) {
chunkmaster.logger.severe("Failed to update table ${table.first} with sql $updateSql")
}
}
}
}

@ -0,0 +1,137 @@
package net.trivernis.chunkmaster.lib
import net.trivernis.chunkmaster.Chunkmaster
import org.apache.commons.lang.exception.ExceptionUtils
import org.sqlite.SQLiteConnection
import java.lang.Exception
import java.sql.Connection
import java.sql.DriverManager
import java.sql.PreparedStatement
import java.sql.ResultSet
class SqliteManager(private val chunkmaster: Chunkmaster) {
private val tables = listOf(
Pair(
"generation_tasks",
listOf(
Pair("id", "integer PRIMARY KEY AUTOINCREMENT"),
Pair("center_x", "integer NOT NULL DEFAULT 0"),
Pair("center_z", "integer NOT NULL DEFAULT 0"),
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")
)
)
)
private val needUpdate = HashSet<Pair<String, Pair<String, String>>>()
private val needCreation = HashSet<String>()
/**
* Returns the connection to the database
*/
fun getConnection(): Connection? {
try {
Class.forName("org.sqlite.JDBC")
return DriverManager.getConnection("jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" +
"${chunkmaster.config.getString("database.filename")}")
} catch (e: Exception) {
chunkmaster.logger.severe("Could not get database connection.")
chunkmaster.logger.severe(e.message)
}
return null
}
/**
* Checks for and performs an update
*/
fun init() {
this.checkUpdate()
this.performUpdate()
}
/**
* Checks which tables need an update or creation.
*/
private fun checkUpdate() {
val meta = getConnection()!!.metaData
for (table in tables) {
val resTables = meta.getTables(null, null, table.first, null)
if (resTables.next()) { // table exists
for (column in table.second) {
val resColumn = meta.getColumns(null, null, table.first, column.first)
if (!resColumn.next()) {
needUpdate.add(Pair(table.first, column))
}
resColumn.close()
}
} else {
needCreation.add(table.first)
}
resTables.close()
}
}
/**
* Executes a sql statement on the database.
*/
fun executeStatement(sql: String, values: HashMap<Int, Any>, callback: ((ResultSet) -> Unit)?) {
val connection = getConnection()
if (connection != null) {
try {
val statement = connection.prepareStatement(sql)
for (parameterValue in values) {
statement.setObject(parameterValue.key, parameterValue.value)
}
statement.execute()
val res = statement.resultSet
if (callback != null) {
callback(res)
}
statement.close()
} catch (e: Exception) {
chunkmaster.logger.severe("An error occured on sql $sql. ${e.message}")
chunkmaster.logger.info(ExceptionUtils.getStackTrace(e))
} finally {
connection.close()
}
} else {
chunkmaster.logger.severe("Could not execute sql $sql. No database connection established.")
}
}
/**
* Creates or updates tables that needed an update.
*/
private fun performUpdate() {
for (table in needCreation) {
try {
var tableDef = "CREATE TABLE IF NOT EXISTS $table ("
for (column in tables.find{it.first == table}!!.second) {
tableDef += "${column.first} ${column.second},"
}
tableDef = tableDef.substringBeforeLast(",") + ");"
chunkmaster.logger.info("Creating table $table with definition $tableDef")
executeStatement(tableDef, HashMap(), null)
} catch (e: Exception) {
chunkmaster.logger.severe("Error creating table $table.")
chunkmaster.logger.severe(e.message)
chunkmaster.logger.info(ExceptionUtils.getStackTrace(e))
}
}
for (table in needUpdate) {
val updateSql = "ALTER TABLE ${table.first} ADD COLUMN ${table.second.first} ${table.second.second}"
try {
executeStatement(updateSql, HashMap(), null)
chunkmaster.logger.info("Updated table ${table.first} with sql $updateSql")
} catch (e: Exception) {
chunkmaster.logger.severe("Failed to update table ${table.first} with sql $updateSql")
chunkmaster.logger.severe(e.message)
chunkmaster.logger.info(ExceptionUtils.getStackTrace(e))
}
}
}
}

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

@ -1,10 +1,10 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.World import org.bukkit.World
class ChunkCoordinates(val x: Int, val z: Int) { class ChunkCoordinates(val x: Int, val z: Int) {
fun getCenterLocation(world: World): Location { fun getCenterLocation(world: World): Location {
return Location(world, ((x*16) + 8).toDouble(), 1.0, ((x*16) + 8).toDouble()) return Location(world, ((x*16) + 8).toDouble(), 1.0, ((x*16) + 8).toDouble())
} }
} }

@ -1,267 +1,264 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
import io.papermc.lib.PaperLib import io.papermc.lib.PaperLib
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.Chunk import org.bukkit.Server
import org.bukkit.Server import org.bukkit.World
import org.bukkit.World
import java.lang.Exception class GenerationManager(private val chunkmaster: Chunkmaster, private val server: Server) {
import java.lang.NullPointerException
val tasks: HashSet<RunningTaskEntry> = HashSet()
class GenerationManager(private val chunkmaster: Chunkmaster, private val server: Server) { val pausedTasks: HashSet<PausedTaskEntry> = HashSet()
val allTasks: HashSet<TaskEntry>
val tasks: HashSet<RunningTaskEntry> = HashSet() get() {
val pausedTasks: HashSet<PausedTaskEntry> = HashSet() val all = HashSet<TaskEntry>()
val allTasks: HashSet<TaskEntry> all.addAll(pausedTasks)
get() { all.addAll(tasks)
val all = HashSet<TaskEntry>() return all
all.addAll(pausedTasks) }
all.addAll(tasks) var paused = false
return all private set
}
var paused = false /**
private set * Adds a generation task
*/
/** fun addTask(world: World, stopAfter: Int = -1): Int {
* Adds a generation task val foundTask = allTasks.find { it.generationTask.world == world }
*/ if (foundTask == null) {
fun addTask(world: World, stopAfter: Int = -1): Int { val centerChunk = ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z)
val foundTask = allTasks.find { it.generationTask.world == world } val generationTask = createGenerationTask(world, centerChunk, centerChunk, stopAfter)
if (foundTask == null) {
val centerChunk = ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) chunkmaster.sqliteManager.executeStatement(
val generationTask = createGenerationTask(world, centerChunk, centerChunk, stopAfter) """
INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, stop_after)
val insertStatement = chunkmaster.sqliteConnection.prepareStatement( values (?, ?, ?, ?, ?, ?)
""" """,
INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, stop_after) HashMap(
values (?, ?, ?, ?, ?, ?) mapOf(
""" 1 to centerChunk.x,
) 2 to centerChunk.z,
insertStatement.setInt(1, centerChunk.x) 3 to centerChunk.x,
insertStatement.setInt(2, centerChunk.z) 4 to centerChunk.z,
insertStatement.setInt(3, centerChunk.x) 5 to world.name,
insertStatement.setInt(4, centerChunk.z) 6 to stopAfter
insertStatement.setString(5, world.name) )
insertStatement.setInt(6, stopAfter) ),
insertStatement.execute() null
)
val getIdStatement = chunkmaster.sqliteConnection.prepareStatement(
""" var id = 0
SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1 chunkmaster.sqliteManager.executeStatement(
""".trimIndent() """
) SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1
getIdStatement.execute() """.trimIndent(),
val result = getIdStatement.resultSet HashMap()
result.next() ) {
val id: Int = result.getInt("id") it.next()
id = it.getInt("id")
insertStatement.close() }
getIdStatement.close()
generationTask.onEndReached {
generationTask.onEndReached { chunkmaster.logger.info("Task #${id} finished after ${it.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(
val task = server.scheduler.runTaskTimer( chunkmaster, generationTask, 200, // 10 sec delay
chunkmaster, generationTask, 200, // 10 sec delay chunkmaster.config.getLong("generation.period")
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)) }
}
return id
return id } else {
} else { return foundTask.id
return foundTask.id }
} }
}
/**
/** * Resumes a generation task
* Resumes a generation task */
*/ private fun resumeTask(
private fun resumeTask( world: World,
world: World, center: ChunkCoordinates,
center: ChunkCoordinates, last: ChunkCoordinates,
last: ChunkCoordinates, id: Int,
id: Int, stopAfter: Int = -1
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(
val task = server.scheduler.runTaskTimer( chunkmaster, generationTask, 200, // 10 sec delay
chunkmaster, generationTask, 200, // 10 sec delay chunkmaster.config.getLong("generation.period")
chunkmaster.config.getLong("generation.period") )
) tasks.add(RunningTaskEntry(id, task, generationTask))
tasks.add(RunningTaskEntry(id, task, generationTask)) generationTask.onEndReached {
generationTask.onEndReached { chunkmaster.logger.info("Task #${id} finished after ${generationTask.count} chunks.")
chunkmaster.logger.info("Task #${id} finished after ${generationTask.count} chunks.") removeTask(id)
removeTask(id) }
} }
} }
}
/**
/** * Stops a running generation task.
* Stops a running generation task. */
*/ 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() chunkmaster.sqliteManager.executeStatement(
val deleteTask = chunkmaster.sqliteConnection.prepareStatement( """
""" DELETE FROM generation_tasks WHERE id = ?;
DELETE FROM generation_tasks WHERE id = ?; """.trimIndent(),
""".trimIndent() HashMap(mapOf(1 to taskEntry.id)),
) null
deleteTask.setInt(1, taskEntry.id) )
deleteTask.execute()
deleteTask.close() if (taskEntry is RunningTaskEntry) {
if (taskEntry.task.isCancelled) {
if (taskEntry is RunningTaskEntry) { tasks.remove(taskEntry)
if (taskEntry.task.isCancelled) { }
tasks.remove(taskEntry) } else if (taskEntry is PausedTaskEntry) {
} pausedTasks.remove(taskEntry)
} else if (taskEntry is PausedTaskEntry) { }
pausedTasks.remove(taskEntry) return true
} }
return true return false
} }
return false
} /**
* Init
/** * Loads tasks from the database and resumes them
* Init */
* Loads tasks from the database and resumes them fun init() {
*/ chunkmaster.logger.info("Creating task to load chunk generation Tasks later...")
fun init() { server.scheduler.runTaskTimer(chunkmaster, Runnable {
chunkmaster.logger.info("Creating task to load chunk generation Tasks later...") saveProgress() // save progress every 30 seconds
server.scheduler.runTaskTimer(chunkmaster, Runnable { }, 600, 600)
saveProgress() // save progress every 30 seconds server.scheduler.runTaskLater(chunkmaster, Runnable {
}, 600, 600) if (server.onlinePlayers.isEmpty()) {
server.scheduler.runTaskLater(chunkmaster, Runnable { startAll() // run startAll after 10 seconds if empty
if (server.onlinePlayers.isEmpty()) { }
startAll() // run startAll after 10 seconds if empty }, 600)
} }
}, 600)
} /**
* Stops all generation tasks
/** */
* Stops all generation tasks fun stopAll() {
*/ saveProgress()
fun stopAll() { val removalSet = HashSet<RunningTaskEntry>()
saveProgress() for (task in tasks) {
val removalSet = HashSet<RunningTaskEntry>() task.generationTask.cancel()
for (task in tasks) { task.task.cancel()
task.generationTask.cancel() if (task.task.isCancelled) {
task.task.cancel() removalSet.add(task)
if (task.task.isCancelled) { }
removalSet.add(task) chunkmaster.logger.info("Canceled task #${task.id}")
} }
chunkmaster.logger.info("Canceled task #${task.id}") tasks.removeAll(removalSet)
} }
tasks.removeAll(removalSet)
} /**
* Starts all generation tasks.
/** */
* Starts all generation tasks. fun startAll() {
*/ chunkmaster.sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) {
fun startAll() { val res = it
val savedTasksStatement = chunkmaster.sqliteConnection.prepareStatement("SELECT * FROM generation_tasks") while (res.next()) {
savedTasksStatement.execute() try {
val res = savedTasksStatement.resultSet val id = res.getInt("id")
while (res.next()) { val world = server.getWorld(res.getString("world"))
try { val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z"))
val id = res.getInt("id") val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z"))
val world = server.getWorld(res.getString("world")) val stopAfter = res.getInt("stop_after")
val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z")) if (this.tasks.find { it.id == id } == null) {
val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z")) resumeTask(world!!, center, last, id, stopAfter)
val stopAfter = res.getInt("stop_after") }
if (this.tasks.find { it.id == id } == null) { } catch (error: NullPointerException) {
resumeTask(world!!, center, last, id, stopAfter) chunkmaster.logger.severe("Failed to load Task ${res.getInt("id")}.")
} }
} catch (error: NullPointerException) { }
chunkmaster.logger.severe("Failed to load Task ${res.getInt("id")}.") }
}
} if (tasks.isNotEmpty()) {
savedTasksStatement.close() chunkmaster.logger.info("${tasks.size} saved tasks loaded.")
if (tasks.isNotEmpty()) { }
chunkmaster.logger.info("${tasks.size} saved tasks loaded.") }
}
} /**
* Pauses all tasks
/** */
* Pauses all tasks fun pauseAll() {
*/ paused = true
fun pauseAll() { for (task in tasks) {
paused = true pausedTasks.add(PausedTaskEntry(task.id, task.generationTask))
for (task in tasks) { }
pausedTasks.add(PausedTaskEntry(task.id, task.generationTask)) stopAll()
} }
stopAll()
} /**
* Resumes all tasks
/** */
* Resumes all tasks fun resumeAll() {
*/ paused = false
fun resumeAll() { pausedTasks.clear()
paused = false startAll()
pausedTasks.clear() }
startAll()
} /**
* Saves the task progress
/** */
* Saves the task progress private fun saveProgress() {
*/ for (task in tasks) {
private fun saveProgress() { try {
for (task in tasks) { val genTask = task.generationTask
try { chunkmaster.logger.info(
val genTask = task.generationTask """Task #${task.id} running for "${genTask.world.name}".
chunkmaster.logger.info( |Progress ${task.generationTask.count} chunks
"""Task #${task.id} running for "${genTask.world.name}". |${if (task.generationTask.stopAfter > 0) "(${"%.2f".format(
|Progress ${task.generationTask.count} chunks (task.generationTask.count.toDouble() /
|${if (task.generationTask.stopAfter > 0) "(${"%.2f".format((task.generationTask.count.toDouble() / task.generationTask.stopAfter.toDouble()) * 100
task.generationTask.stopAfter.toDouble()) * 100)}%)" else ""}. )}%)" else ""}.
| Speed: ${"%.1f".format(task.generationSpeed)} chunks/sec, | Speed: ${"%.1f".format(task.generationSpeed)} chunks/sec,
|Last Chunk: ${genTask.lastChunk.x}, ${genTask.lastChunk.z}""".trimMargin("|").replace('\n', ' ') |Last Chunk: ${genTask.lastChunk.x}, ${genTask.lastChunk.z}""".trimMargin("|").replace('\n', ' ')
) )
val updateStatement = chunkmaster.sqliteConnection.prepareStatement( chunkmaster.sqliteManager.executeStatement(
""" """
UPDATE generation_tasks SET last_x = ?, last_z = ? UPDATE generation_tasks SET last_x = ?, last_z = ?
WHERE id = ? WHERE id = ?
""".trimIndent() """.trimIndent(),
) HashMap(mapOf(1 to genTask.lastChunk.x, 2 to genTask.lastChunk.z, 3 to task.id)),
updateStatement.setInt(1, genTask.lastChunk.x) null
updateStatement.setInt(2, genTask.lastChunk.z) )
updateStatement.setInt(3, task.id) } catch (error: Exception) {
updateStatement.execute() chunkmaster.logger.warning("Exception when saving task progress ${error.message}")
updateStatement.close() }
} catch (error: Exception) { }
chunkmaster.logger.warning("Exception when saving task progress ${error.message}") }
}
} /**
} * Creates a new generation task. This method is used to create a task depending
* on the server type (Paper/Spigot).
/** */
* Creates a new generation task. This method is used to create a task depending private fun createGenerationTask(
* on the server type (Paper/Spigot). world: World,
*/ center: ChunkCoordinates,
private fun createGenerationTask( start: ChunkCoordinates,
world: World, stopAfter: Int
center: ChunkCoordinates, ): GenerationTask {
start: ChunkCoordinates, return if (PaperLib.isPaper()) {
stopAfter: Int GenerationTaskPaper(chunkmaster, world, center, start, stopAfter)
): GenerationTask { } else {
return if (PaperLib.isPaper()) { GenerationTaskSpigot(chunkmaster, world, center, start, stopAfter)
GenerationTaskPaper(chunkmaster, world, center, start, stopAfter) }
} else { }
GenerationTaskSpigot(chunkmaster, world, center, start, stopAfter)
}
}
} }

@ -1,64 +1,66 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Spiral import net.trivernis.chunkmaster.lib.Spiral
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.World import org.bukkit.World
/** /**
* Interface for generation tasks. * Interface for generation tasks.
*/ */
abstract class GenerationTask(plugin: Chunkmaster, centerChunk: ChunkCoordinates, startChunk: ChunkCoordinates) : abstract class GenerationTask(plugin: Chunkmaster, centerChunk: ChunkCoordinates, startChunk: ChunkCoordinates) :
Runnable { Runnable {
abstract val stopAfter: Int abstract val stopAfter: Int
abstract val world: World abstract val world: World
abstract val count: Int abstract val count: Int
abstract val endReached: Boolean abstract val endReached: Boolean
protected val spiral: Spiral = protected val spiral: Spiral =
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.chunk-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 val chunksPerStep = plugin.config.getInt("generation.chunks-per-step")
protected val ignoreWorldborder = plugin.config.getBoolean("generation.ignore-worldborder")
protected var endReachedCallback: (() -> Unit)? = null
private set protected var endReachedCallback: ((GenerationTask) -> Unit)? = null
private set
abstract override fun run()
abstract fun cancel() abstract override fun run()
abstract fun cancel()
val nextChunkCoordinates: ChunkCoordinates
get() { val nextChunkCoordinates: ChunkCoordinates
val nextChunkCoords = spiral.next() get() {
return ChunkCoordinates(nextChunkCoords.first, nextChunkCoords.second) val nextChunkCoords = spiral.next()
} return ChunkCoordinates(nextChunkCoords.first, nextChunkCoords.second)
}
val lastChunk: Chunk
get() { val lastChunk: Chunk
return world.getChunkAt(lastChunkCoords.x, lastChunkCoords.z) get() {
} return world.getChunkAt(lastChunkCoords.x, lastChunkCoords.z)
}
val nextChunk: Chunk
get() { val nextChunk: Chunk
val next = nextChunkCoordinates get() {
return world.getChunkAt(next.x, next.z) val next = nextChunkCoordinates
} return world.getChunkAt(next.x, next.z)
}
/**
* Checks if the World border or the maximum chunk setting for the task is reached. /**
*/ * Checks if the World border or the maximum chunk setting for the task is reached.
protected fun borderReached(): Boolean { */
return !world.worldBorder.isInside(lastChunkCoords.getCenterLocation(world)) || (stopAfter in 1..count) protected fun borderReached(): Boolean {
} return (!world.worldBorder.isInside(lastChunkCoords.getCenterLocation(world)) && !ignoreWorldborder)
|| (stopAfter in 1..count)
/** }
* Registers end reached callback
*/ /**
fun onEndReached(cb: () -> Unit) { * Registers end reached callback
endReachedCallback = cb */
} fun onEndReached(cb: (GenerationTask) -> Unit) {
endReachedCallback = cb
}
} }

@ -1,117 +1,116 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.World import org.bukkit.World
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
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: ChunkCoordinates, private val startChunk: ChunkCoordinates,
centerChunk: ChunkCoordinates, private val startChunk: ChunkCoordinates, override val stopAfter: Int = -1
override val stopAfter: Int = -1 ) : GenerationTask(plugin, centerChunk, startChunk) {
) : GenerationTask(plugin, centerChunk, startChunk) {
private val maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks")
private val maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks")
private val pendingChunks = HashSet<CompletableFuture<Chunk>>()
private val pendingChunks = HashSet<CompletableFuture<Chunk>>()
override var count = 0
override var count = 0 private set
private set override var endReached: Boolean = false
override var endReached: Boolean = false private set
private set
/**
/** * Runs the generation task. Every Iteration the next chunk will be generated if
* Runs the generation task. Every Iteration the next chunk will be generated if * it hasn't been generated already.
* it hasn't been generated already. * 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 < msptThreshold) { // pause when tps < 2
if (plugin.mspt < msptThreshold) { // pause when tps < 2 if (loadedChunks.size > maxLoadedChunks) {
if (loadedChunks.size > maxLoadedChunks) { for (chunk in loadedChunks) {
for (chunk in loadedChunks) { if (chunk.isLoaded) {
if (chunk.isLoaded) { chunk.unload(true)
chunk.unload(true) }
} }
} loadedChunks.clear()
loadedChunks.clear() } else if (pendingChunks.size < maxPendingChunks) { // if more than 10 chunks are pending, wait.
} else if (pendingChunks.size < maxPendingChunks) { // if more than 10 chunks are pending, wait. if (borderReached()) {
if (borderReached()) { endReached = true
endReached = true endReachedCallback?.invoke(this)
endReachedCallback?.invoke() return
return }
}
var chunk = nextChunkCoordinates
var chunk = nextChunkCoordinates for (i in 1 until chunkSkips) {
for (i in 1 until chunkSkips) { if (world.isChunkGenerated(chunk.x, chunk.z)) {
if (PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { chunk = nextChunkCoordinates
chunk = nextChunkCoordinates } else {
} else { break
break }
} }
}
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { for (i in 0 until minOf(chunksPerStep, (stopAfter - count) - 1)) {
for (i in 0 until minOf(chunksPerStep, (stopAfter - count) - 1)) { if (!world.isChunkGenerated(chunk.x, chunk.z)) {
if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { pendingChunks.add(world.getChunkAtAsync(chunk.x, chunk.z, true))
pendingChunks.add(PaperLib.getChunkAtAsync(world, chunk.x, chunk.z, true)) }
} chunk = nextChunkCoordinates
chunk = nextChunkCoordinates }
} if (!world.isChunkGenerated(chunk.x, chunk.z)) {
if (!PaperLib.isChunkGenerated(world, chunk.x, chunk.z)) { pendingChunks.add(world.getChunkAtAsync(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 }
} }
} checkChunksLoaded()
checkChunksLoaded() }
}
/**
/** * Cancels the generation task.
* Cancels the generation task. * 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()
unloadAllChunks() }
}
/**
/** * Cancels all pending chunks and unloads all loaded chunks.
* Cancels all pending chunks and unloads all loaded chunks. */
*/ fun unloadAllChunks() {
fun unloadAllChunks() { for (pendingChunk in pendingChunks) {
for (pendingChunk in pendingChunks) { if (pendingChunk.isDone) {
if (pendingChunk.isDone) { loadedChunks.add(pendingChunk.get())
loadedChunks.add(pendingChunk.get()) } else {
} else { pendingChunk.cancel(true)
pendingChunk.cancel(true) }
} }
} pendingChunks.clear()
pendingChunks.clear() if (loadedChunks.isNotEmpty()) {
if (loadedChunks.isNotEmpty()) { lastChunkCoords = ChunkCoordinates(loadedChunks.last().x, loadedChunks.last().z)
lastChunkCoords = ChunkCoordinates(loadedChunks.last().x, loadedChunks.last().z) }
} for (chunk in loadedChunks) {
for (chunk in loadedChunks) { if (chunk.isLoaded) {
if (chunk.isLoaded) { chunk.unload(true)
chunk.unload(true) }
} }
} }
}
/**
/** * Checks if some chunks have been loaded and adds them to the loaded chunk set.
* Checks if some chunks have been loaded and adds them to the loaded chunk set. */
*/ private fun checkChunksLoaded() {
private fun checkChunksLoaded() { val completedEntrys = HashSet<CompletableFuture<Chunk>>()
val completedEntrys = HashSet<CompletableFuture<Chunk>>() for (pendingChunk in pendingChunks) {
for (pendingChunk in pendingChunks) { if (pendingChunk.isDone) {
if (pendingChunk.isDone) { completedEntrys.add(pendingChunk)
completedEntrys.add(pendingChunk) loadedChunks.add(pendingChunk.get())
loadedChunks.add(pendingChunk.get()) } else if (pendingChunk.isCompletedExceptionally || pendingChunk.isCancelled) {
} else if (pendingChunk.isCompletedExceptionally || pendingChunk.isCancelled) { completedEntrys.add(pendingChunk)
completedEntrys.add(pendingChunk) }
} }
} pendingChunks.removeAll(completedEntrys)
pendingChunks.removeAll(completedEntrys) }
}
} }

@ -1,70 +1,69 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.Chunk import org.bukkit.World
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: ChunkCoordinates, private val startChunk: ChunkCoordinates,
centerChunk: ChunkCoordinates, private val startChunk: ChunkCoordinates, override val stopAfter: Int = -1
override val stopAfter: Int = -1 ) : GenerationTask(plugin, centerChunk, startChunk) {
) : GenerationTask(plugin, centerChunk, startChunk) {
override var count = 0
override var count = 0 private set
private set override var endReached: Boolean = false
override var endReached: Boolean = false private set
private set
/**
/** * Runs the generation task. Every Iteration the next chunk will be generated if
* Runs the generation task. Every Iteration the next chunk will be generated if * it hasn't been generated already.
* it hasn't been generated already. * 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 < msptThreshold) { // pause when tps < 2
if (plugin.mspt < msptThreshold) { // pause when tps < 2 if (loadedChunks.size > maxLoadedChunks) {
if (loadedChunks.size > maxLoadedChunks) { for (chunk in loadedChunks) {
for (chunk in loadedChunks) { if (chunk.isLoaded) {
if (chunk.isLoaded) { chunk.unload(true)
chunk.unload(true) }
} }
} loadedChunks.clear()
loadedChunks.clear() } else {
} else { if (borderReached()) {
if (borderReached()) { endReached = true
endReached = true endReachedCallback?.invoke(this)
endReachedCallback?.invoke() return
return }
}
var 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)) {
for (i in 0 until minOf(chunksPerStep, stopAfter - count)) { 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) chunk = nextChunkCoordinates
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) }
} 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 }
} }
} }
}
/**
/** * Cancels the generation task.
* Cancels the generation task. * 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() { for (chunk in loadedChunks) {
for (chunk in loadedChunks) { if (chunk.isLoaded) {
if (chunk.isLoaded) { chunk.unload(true)
chunk.unload(true) }
} }
} }
}
} }

@ -1,10 +1,10 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
class PausedTaskEntry( class PausedTaskEntry(
override val id: Int, override val id: Int,
override val generationTask: GenerationTask override val generationTask: GenerationTask
) : TaskEntry { ) : TaskEntry {
override fun cancel() { override fun cancel() {
generationTask.cancel() generationTask.cancel()
} }
} }

@ -1,37 +1,37 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
class RunningTaskEntry( class RunningTaskEntry(
override val id: Int, override val id: Int,
val task: BukkitTask, val task: BukkitTask,
override val generationTask: GenerationTask override val generationTask: GenerationTask
) : TaskEntry { ) : TaskEntry {
private var lastProgress: Pair<Long, Int>? = null private var lastProgress: Pair<Long, Int>? = null
/** /**
* Returns the generation Speed * Returns the generation Speed
*/ */
val generationSpeed: Double? val generationSpeed: Double?
get() { get() {
var generationSpeed: Double? = null var generationSpeed: Double? = null
if (lastProgress != null) { if (lastProgress != null) {
val chunkDiff = generationTask.count - lastProgress!!.second val chunkDiff = generationTask.count - lastProgress!!.second
val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble()/1000 val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble()/1000
generationSpeed = chunkDiff.toDouble()/timeDiff generationSpeed = chunkDiff.toDouble()/timeDiff
} }
lastProgress = Pair(System.currentTimeMillis(), generationTask.count) lastProgress = Pair(System.currentTimeMillis(), generationTask.count)
return generationSpeed return generationSpeed
} }
init { init {
lastProgress = Pair(System.currentTimeMillis(), generationTask.count) lastProgress = Pair(System.currentTimeMillis(), generationTask.count)
} }
override fun cancel() { override fun cancel() {
task.cancel() task.cancel()
generationTask.cancel() generationTask.cancel()
} }
} }

@ -1,13 +1,13 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
/** /**
* Generic task entry * Generic task entry
*/ */
interface TaskEntry { interface TaskEntry {
val id: Int val id: Int
val generationTask: GenerationTask val generationTask: GenerationTask
fun cancel() fun cancel()
} }

@ -1,58 +1,58 @@
main: net.trivernis.chunkmaster.Chunkmaster main: net.trivernis.chunkmaster.Chunkmaster
name: Chunkmaster name: Chunkmaster
version: '0.12-beta' version: '0.13-beta'
description: Chunk commands plugin. description: Chunk commands plugin.
author: Trivernis author: Trivernis
website: trivernis.net website: trivernis.net
api-version: '1.14' api-version: '1.14'
commands: commands:
chunkmaster: chunkmaster:
description: Main command description: Main command
permission: chunkmaster.chunkmaster permission: chunkmaster.chunkmaster
usage: | usage: |
/<command> generate [<world>, <chunk-count>] - generates chunks starting from the spawn until the chunk-count is reached /<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> cancel <task-id> - cancels the generation task with the task-id
/<command> list - lists all running and paused generation tasks /<command> list - lists all running and paused generation tasks
/<command> pause - pauses all generation tasks /<command> pause - pauses all generation tasks
/<command> resume - resumes all generation tasks /<command> resume - resumes all generation tasks
/<command> reload - reloads the configuration and restarts all tasks /<command> reload - reloads the configuration and restarts all tasks
/<command> tpchunk <chunkX> <chunkZ> - teleports you to the chunk with the given chunk coordinates /<command> tpchunk <chunkX> <chunkZ> - teleports you to the chunk with the given chunk coordinates
aliases: aliases:
- chm - chm
- chunkm - chunkm
- cmaster - cmaster
permissions: permissions:
cunkmaster.generate: cunkmaster.generate:
description: Allows the generate subcommand. description: Allows the generate subcommand.
default: op default: op
chunkmaster.list: chunkmaster.list:
description: Allows the list subcommand. description: Allows the list subcommand.
default: op default: op
chunkmaster.cancel: chunkmaster.cancel:
description: Allows the remove subcommand. description: Allows the remove subcommand.
default: op default: op
chunkmaster.pause: chunkmaster.pause:
description: Allows the pause subcommand. description: Allows the pause subcommand.
default: op default: op
chunkmaster.resume: chunkmaster.resume:
description: Allows the resume subcommand. description: Allows the resume subcommand.
default: op default: op
chunkmaster.reload: chunkmaster.reload:
description: Allows the reload subcommand. description: Allows the reload subcommand.
default: op default: op
chunkmaster.tpchunk: chunkmaster.tpchunk:
description: Allows the tpchunk subcommand. description: Allows the tpchunk subcommand.
default: op default: op
chunkmaster.chunkmaster: chunkmaster.chunkmaster:
description: Allows Chunkmaster commands. description: Allows Chunkmaster commands.
default: op default: op
chunkmaster.*: chunkmaster.*:
description: Wildcard permission description: Wildcard permission
default: op default: op
children: children:
- chunkmaster.generate - chunkmaster.generate
- chunkmaster.listgentasks - chunkmaster.listgentasks
- chunkmaster.removegentask - chunkmaster.removegentask
- chunkmaster.pausegentasks - chunkmaster.pausegentasks
- chunkmaster.resumegentasks - chunkmaster.resumegentasks
- chunkmaster.chunkmaster - chunkmaster.chunkmaster
Loading…
Cancel
Save