Merge pull request #109 from Trivernis/develop

1.4.0 Fixes and additions
master v1.4.0
Trivernis 4 years ago committed by GitHub
commit ebab533e08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,36 @@
name: Gradle Unit Tests
on:
workflow_dispatch:
push:
branches: [ main, develop, actions, feature/unit-tests ]
pull_request:
branches: [ main, develop, actions, feature/unit-tests ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Cache build data
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('build.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Test
run: chmod +x gradlew && ./gradlew test
- name: Cleanup Gradle Cache
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties

@ -22,9 +22,7 @@ idea {
}
group "net.trivernis"
version PLUGIN_VERSION
sourceCompatibility = 1.8
repositories {
@ -48,16 +46,26 @@ repositories {
name 'mikeprimm'
url 'https://repo.mikeprimm.com'
}
maven {
url 'https://jitpack.io'
}
}
dependencies {
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compileOnly "com.destroystokyo.paper:paper-api:1.14.4-R0.1-SNAPSHOT"
compileOnly "org.dynmap:dynmap-api:2.0"
compileOnly group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "io.papermc:paperlib:1.0.6"
implementation "org.bstats:bstats-bukkit:1.5"
testImplementation group: 'junit', name: 'junit', version: '4.12'
testImplementation 'io.kotest:kotest-runner-junit5:4.3.2'
testImplementation 'io.mockk:mockk:1.10.6'
testImplementation "org.dynmap:dynmap-api:2.0"
}
apply plugin: "com.github.johnrengelman.shadow"
@ -66,13 +74,16 @@ apply plugin: 'java'
shadowJar {
relocate 'io.papermc.lib', 'net.trivernis.chunkmaster.paperlib'
relocate 'org.bstats', 'net.trivernis.chunkmaster.bstats'
relocate 'kotlin', 'net.trivernis.chunkmaster.kotlin'
relocate 'org.intellij', 'net.trivernis.chunkmaster.intellij'
relocate 'org.jetbrains', 'net.trivernis.chunkmaster.jetbrains'
}
processResources {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
with copySpec {
from 'src/main/resources/'
include '**/*'
include 'plugin.yml'
filter { String line ->
line.replace('$$PLUGIN_VERSION$$', PLUGIN_VERSION)
}
@ -89,7 +100,4 @@ compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
dependencies {
compileOnly group: 'junit', name: 'junit', version: '4.12'
}
}

@ -1,2 +1,2 @@
kotlin.code.style=official
PLUGIN_VERSION=1.3.4
PLUGIN_VERSION=1.4.0

@ -9,10 +9,8 @@ import org.bstats.bukkit.Metrics
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scheduler.BukkitTask
import org.dynmap.DynmapAPI
import java.lang.IllegalStateException
import java.lang.NullPointerException
class Chunkmaster : JavaPlugin() {
open class Chunkmaster : JavaPlugin() {
lateinit var sqliteManager: SqliteManager
lateinit var generationManager: GenerationManager
lateinit var langManager: LanguageManager
@ -32,7 +30,7 @@ class Chunkmaster : JavaPlugin() {
logger.finest("LogLevel: FINEST")
configure()
val metrics = Metrics(this)
Metrics(this)
langManager = LanguageManager(this)
langManager.loadProperties()

@ -44,7 +44,7 @@ class CmdCancel(private val chunkmaster: Chunkmaster) : Subcommand {
}
} else {
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ID_REQUIRED"));
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ID_REQUIRED"))
false
}
}

@ -0,0 +1,44 @@
package net.trivernis.chunkmaster.commands
import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
class CmdCompleted(private val plugin: Chunkmaster) : Subcommand {
override val name = "completed"
override fun execute(sender: CommandSender, args: List<String>): Boolean {
plugin.sqliteManager.completedGenerationTasks.getCompletedTasks().thenAccept { tasks ->
val worlds = tasks.map { it.world }.toHashSet()
var response = "\n" + plugin.langManager.getLocalized("COMPLETED_TASKS_HEADER") + "\n\n"
for (world in worlds) {
response += plugin.langManager.getLocalized("COMPLETED_WORLD_HEADER", world) + "\n"
for (task in tasks.filter { it.world == world }) {
response += plugin.langManager.getLocalized(
"COMPLETED_TASK_ENTRY",
task.id,
task.radius,
task.center.x,
task.center.z,
task.shape
) + "\n"
}
response += "\n"
}
sender.sendMessage(response)
}
return true
}
override fun onTabComplete(
sender: CommandSender,
command: Command,
alias: String,
args: List<String>
): MutableList<String> {
return mutableListOf()
}
}

@ -113,7 +113,7 @@ class CmdGenerate(private val chunkmaster: Chunkmaster) : Subcommand {
)
true
} else if (world == null) {
sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName));
sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName))
false
} else {
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ALREADY_EXISTS", worldName))

@ -7,7 +7,7 @@ import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
override val name = "getCenter";
override val name = "getCenter"
override fun onTabComplete(
sender: CommandSender,
@ -27,7 +27,7 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
if (args.isNotEmpty()) {
args[0]
} else {
sender.world.name;
sender.world.name
}
} else {
if (args.isEmpty()) {

@ -6,7 +6,6 @@ import net.trivernis.chunkmaster.lib.generation.taskentry.TaskEntry
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import kotlin.math.ceil
import kotlin.math.pow
class CmdList(private val chunkmaster: Chunkmaster) : Subcommand {
override val name = "list"
@ -50,7 +49,8 @@ class CmdList(private val chunkmaster: Chunkmaster) : Subcommand {
*/
private fun getGenerationEntry(task: TaskEntry): String {
val genTask = task.generationTask
val progress = genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null)
val progress =
genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null)
val percentage = " (%.1f".format(progress * 100) + "%)."
val count = if (genTask.radius > 0) {

@ -7,7 +7,7 @@ import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
override val name = "setCenter";
override val name = "setCenter"
override fun onTabComplete(
sender: CommandSender,
@ -21,7 +21,7 @@ class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
.map { it.name }.toMutableList()
}
}
return emptyList<String>().toMutableList();
return emptyList<String>().toMutableList()
}
override fun execute(sender: CommandSender, args: List<String>): Boolean {

@ -37,12 +37,16 @@ class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand {
}
private fun getWorldStatsMessage(sender: CommandSender, world: World): String {
var message = """
return """
${chunkmaster.langManager.getLocalized("STATS_WORLD_NAME", world.name)}
${chunkmaster.langManager.getLocalized("STATS_ENTITY_COUNT", world.entities.size)}
${chunkmaster.langManager.getLocalized("STATS_LOADED_CHUNKS", world.loadedChunks.size)}
${
chunkmaster.langManager.getLocalized(
"STATS_GENERATING",
chunkmaster.generationManager.tasks.find { it.generationTask.world == world } != null)
}
""".trimIndent()
return message
}
private fun getServerStatsMessage(sender: CommandSender): String {
@ -54,15 +58,22 @@ class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand {
${chunkmaster.langManager.getLocalized("STATS_SERVER")}
${chunkmaster.langManager.getLocalized("STATS_SERVER_VERSION", sender.server.version)}
${chunkmaster.langManager.getLocalized("STATS_PLUGIN_VERSION", chunkmaster.description.version)}
${chunkmaster.langManager.getLocalized(
${
chunkmaster.langManager.getLocalized(
"STATS_MEMORY",
memUsed / 1000000,
runtime.maxMemory() / 1000000,
(memUsed.toFloat() / runtime.maxMemory().toFloat()) * 100
)}
)
}
${chunkmaster.langManager.getLocalized("STATS_CORES", runtime.availableProcessors())}
${chunkmaster.langManager.getLocalized("STATS_PLUGIN_LOADED_CHUNKS", chunkmaster.generationManager.loadedChunkCount)}
${
chunkmaster.langManager.getLocalized(
"STATS_PLUGIN_LOADED_CHUNKS",
chunkmaster.generationManager.loadedChunkCount
)
}
""".trimIndent()
for (world in sender.server.worlds) {
message += "\n\n" + getWorldStatsMessage(sender, world)

@ -1,6 +1,7 @@
package net.trivernis.chunkmaster.commands
import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.ArgParser
import net.trivernis.chunkmaster.lib.Subcommand
import org.bukkit.Server
import org.bukkit.command.Command
@ -11,6 +12,7 @@ import org.bukkit.command.TabCompleter
class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val server: Server) : CommandExecutor,
TabCompleter {
private val commands = HashMap<String, Subcommand>()
private val argParser = ArgParser()
init {
registerCommands()
@ -36,7 +38,14 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
/**
* /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,
bukkitArgs: Array<out String>
): Boolean {
val args = argParser.parseArguments(bukkitArgs.joinToString(" "))
if (args.isNotEmpty()) {
if (sender.hasPermission("chunkmaster.${args[0].toLowerCase()}")) {
return if (commands.containsKey(args[0])) {
@ -87,5 +96,8 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
val cmdStats = CmdStats(chunkmaster)
commands[cmdStats.name] = cmdStats
val cmdCompleted = CmdCompleted(chunkmaster)
commands[cmdCompleted.name] = cmdCompleted
}
}

@ -0,0 +1,92 @@
package net.trivernis.chunkmaster.lib
/**
* Better argument parser for command arguments
*/
class ArgParser {
private var input = ""
private var position = 0
private var currentChar = ' '
private var escaped = false
/**
* Parses arguments from a string and respects quotes
*/
fun parseArguments(arguments: String): List<String> {
if (arguments.isEmpty()) {
return emptyList()
}
input = arguments
position = 0
currentChar = input[position]
escaped = false
val args = ArrayList<String>()
var arg = ""
while (!endReached()) {
nextCharacter()
if (currentChar == '\\' && !escaped) {
escaped = true
continue
}
if (currentChar.isWhitespace() && !escaped) {
if (arg.isNotBlank()) {
args.add(arg)
}
arg = ""
} else if (currentChar == '"' && !escaped) {
if (arg.isNotBlank()) {
args.add(arg)
}
arg = parseString()
if (arg.isNotBlank()) {
args.add(arg)
}
arg = ""
} else {
arg += currentChar
}
escaped = false
}
if (arg.isNotBlank()) {
args.add(arg)
}
return args
}
/**
* Parses an enquoted string
*/
private fun parseString(): String {
var output = ""
while (!endReached()) {
nextCharacter()
if (currentChar == '\\') {
escaped = !escaped
continue
}
if (currentChar == '"' && !escaped) {
break
}
output += currentChar
escaped = false
}
return output
}
private fun nextCharacter() {
if (!endReached()) {
currentChar = input[position++]
}
}
private fun endReached(): Boolean {
return position >= input.length
}
}

@ -0,0 +1,11 @@
package net.trivernis.chunkmaster.lib.database
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
data class CompletedGenerationTask(
val id: Int,
val world: String,
val radius: Int,
val center: ChunkCoordinates,
val shape: String
)

@ -0,0 +1,80 @@
package net.trivernis.chunkmaster.lib.database
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
import java.sql.ResultSet
import java.util.concurrent.CompletableFuture
class CompletedGenerationTasks(private val sqliteManager: SqliteManager) {
/**
* Returns the list of all completed tasks
*/
fun getCompletedTasks(): CompletableFuture<List<CompletedGenerationTask>> {
val completableFuture = CompletableFuture<List<CompletedGenerationTask>>()
sqliteManager.executeStatement("SELECT * FROM completed_generation_tasks", HashMap()) { res ->
val tasks = ArrayList<CompletedGenerationTask>()
while (res!!.next()) {
tasks.add(mapSqlResponseToWrapperObject(res))
}
completableFuture.complete(tasks)
}
return completableFuture
}
/**
* Returns a list of completed tasks for a world
*/
fun getCompletedTasksForWorld(world: String): CompletableFuture<List<CompletedGenerationTask>> {
val completableFuture = CompletableFuture<List<CompletedGenerationTask>>()
sqliteManager.executeStatement(
"SELECT * FROM completed_generation_tasks WHERE world = ?",
hashMapOf(1 to world)
) { res ->
val tasks = ArrayList<CompletedGenerationTask>()
while (res!!.next()) {
tasks.add(mapSqlResponseToWrapperObject(res))
}
completableFuture.complete(tasks)
}
return completableFuture
}
private fun mapSqlResponseToWrapperObject(res: ResultSet): CompletedGenerationTask {
val id = res.getInt("id")
val world = res.getString("world")
val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z"))
val radius = res.getInt("completed_radius")
val shape = res.getString("shape")
return CompletedGenerationTask(id, world, radius, center, shape)
}
/**
* Adds a completed task
*/
fun addCompletedTask(
id: Int,
world: String,
radius: Int,
center: ChunkCoordinates,
shape: String
): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
sqliteManager.executeStatement(
"INSERT INTO completed_generation_tasks (id, world, completed_radius, center_x, center_z, shape) VALUES (?, ?, ?, ?, ?, ?)",
hashMapOf(
1 to id,
2 to world,
3 to radius,
4 to center.x,
5 to center.z,
6 to shape,
)
) {
completableFuture.complete(null)
}
return completableFuture
}
}

@ -7,6 +7,7 @@ import kotlin.math.ceil
class PendingChunks(private val sqliteManager: SqliteManager) {
private val insertionCount = 300
/**
* Returns a list of pending chunks for a taskId
*/
@ -38,7 +39,15 @@ class PendingChunks(private val sqliteManager: SqliteManager) {
val statementCount = ceil(pendingChunks.size.toDouble() / insertionCount).toInt()
for (i in 0 until statementCount) {
futures.add(insertPendingChunks(taskId, pendingChunks.subList(i * insertionCount, ((i * insertionCount) + insertionCount).coerceAtMost(pendingChunks.size))))
futures.add(
insertPendingChunks(
taskId,
pendingChunks.subList(
i * insertionCount,
((i * insertionCount) + insertionCount).coerceAtMost(pendingChunks.size)
)
)
)
}
if (futures.size > 0) {

@ -38,6 +38,17 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
Pair("chunk_x", "integer NOT NULL"),
Pair("chunk_z", "integer NOT NULL")
)
),
Pair(
"completed_generation_tasks",
listOf(
Pair("id", "integer PRIMARY KEY"),
Pair("world", "text NOT NULL"),
Pair("completed_radius", "integer NOT NULL"),
Pair("center_x", "integer NOT NULL"),
Pair("center_z", "integer NOT NULL"),
Pair("shape", "text NOT NULL")
)
)
)
private val needUpdate = HashSet<Pair<String, Pair<String, String>>>()
@ -48,6 +59,7 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
val worldProperties = WorldProperties(this)
val pendingChunks = PendingChunks(this)
val generationTasks = GenerationTasks(this)
val completedGenerationTasks = CompletedGenerationTasks(this)
/**
* Returns the connection to the database

@ -5,7 +5,7 @@ import net.trivernis.chunkmaster.lib.generation.taskentry.PausedTaskEntry
import net.trivernis.chunkmaster.lib.generation.taskentry.RunningTaskEntry
import net.trivernis.chunkmaster.lib.generation.taskentry.TaskEntry
import net.trivernis.chunkmaster.lib.shapes.Circle
import net.trivernis.chunkmaster.lib.shapes.Spiral
import net.trivernis.chunkmaster.lib.shapes.Square
import org.bukkit.Server
import org.bukkit.World
import java.util.concurrent.CompletableFuture
@ -17,6 +17,8 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
val worldProperties = chunkmaster.sqliteManager.worldProperties
private val pendingChunksTable = chunkmaster.sqliteManager.pendingChunks
private val generationTasks = chunkmaster.sqliteManager.generationTasks
private val completedGenerationTasks = chunkmaster.sqliteManager.completedGenerationTasks
private val unloadingPeriod: Long
get() {
return chunkmaster.config.getLong("generation.unloading-period")
@ -140,6 +142,13 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
taskEntry.cancel(chunkmaster.config.getLong("mspt-pause-threshold"))
}
generationTasks.deleteGenerationTask(id)
completedGenerationTasks.addCompletedTask(
id,
taskEntry.generationTask.world.name,
taskEntry.generationTask.shape.currentRadius(),
taskEntry.generationTask.startChunk,
taskEntry.generationTask.shape.javaClass.simpleName
)
pendingChunksTable.clearPendingChunks(id)
if (taskEntry is RunningTaskEntry) {
@ -292,7 +301,8 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
private fun reportGenerationProgress(task: RunningTaskEntry) {
val genTask = task.generationTask
val (speed, chunkSpeed) = task.generationSpeed
val progress = genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null)
val progress =
genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null)
val percentage =
"(${"%.2f".format(progress * 100)}%)"
@ -356,8 +366,8 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
): GenerationTask {
val shape = when (shapeName) {
"circle" -> Circle(Pair(center.x, center.z), Pair(start.x, start.z), radius)
"square" -> Spiral(Pair(center.x, center.z), Pair(start.x, start.z), radius)
else -> Spiral(Pair(center.x, center.z), Pair(start.x, start.z), radius)
"square" -> Square(Pair(center.x, center.z), Pair(start.x, start.z), radius)
else -> Square(Pair(center.x, center.z), Pair(start.x, start.z), radius)
}
return DefaultGenerationTask(

@ -10,10 +10,10 @@ import kotlin.math.ceil
* Interface for generation tasks.
*/
abstract class GenerationTask(
private val plugin: Chunkmaster,
plugin: Chunkmaster,
val world: World,
protected val unloader: ChunkUnloader,
startChunk: ChunkCoordinates,
val startChunk: ChunkCoordinates,
val shape: Shape,
val missingChunks: HashSet<ChunkCoordinates>,
var state: TaskState

@ -18,7 +18,8 @@ class RunningTaskEntry(
get() {
var generationSpeed: Double? = null
var chunkGenerationSpeed: Double? = null
val progress = generationTask.shape.progress(if (generationTask.radius < 0) (generationTask.world.worldBorder.size / 32).toInt() else null)
val progress =
generationTask.shape.progress(if (generationTask.radius < 0) (generationTask.world.worldBorder.size / 32).toInt() else null)
if (lastProgress != null) {
val progressDiff = progress - lastProgress!!.second
val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble() / 1000

@ -3,7 +3,7 @@ package net.trivernis.chunkmaster.lib.shapes
import kotlin.math.abs
import kotlin.math.pow
class Spiral(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape(center, start, radius) {
class Square(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape(center, start, radius) {
private var direction = 0
override fun endReached(): Boolean {
@ -40,7 +40,7 @@ class Spiral(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
}
if (count == 0 && currentPos != center) {
// simulate the spiral to get the correct direction and count
val simSpiral = Spiral(center, center, radius)
val simSpiral = Square(center, center, radius)
while (simSpiral.next() != currentPos && !simSpiral.endReached());
direction = simSpiral.direction
count = simSpiral.count

@ -23,6 +23,10 @@ TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%s§r - §2%s chunks %s§r
RUNNING_TASKS_HEADER = Currently Running Generation Tasks
NO_GENERATION_TASKS = There are no generation tasks.
COMPLETED_TASKS_HEADER = §nCompleted Generation Tasks§r
COMPLETED_WORLD_HEADER = §l%s§r
COMPLETED_TASK_ENTRY = - §9#%d§r: §2%d§r chunks radius from center §2(%d, %d)§r with shape §2%s§r
PAUSE_SUCCESS = §9Paused all generation tasks.
ALREADY_PAUSED = §cThe generation process is already paused!
@ -74,6 +78,7 @@ STATS_WORLD_NAME = §l%s§r
STATS_ENTITY_COUNT = - §2%d§r Entities
STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks
STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster
STATS_GENERATING = - Generating: §2%s§r
SAVING_CHUNKS = Saving %d loaded chunks...
CANCEL_FAIL = Failed to cancel task #%d in the given timeout!

@ -78,3 +78,9 @@ STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r von Chunkmaster geladene Chunks
SAVING_CHUNKS = Speichere %d geladene Chunks...
CANCEL_FAIL = Konnte Aufgabe #%d nicht im angegebenen Timeout stoppen!
NO_AUTOSTART = Autostart ist auf §2false§r gesetzt. Pausiere...
COMPLETED_TASKS_HEADER = §nAbgeschlossene Aufgaben§r
COMPLETED_WORLD_HEADER = §l%s§r
COMPLETED_TASK_ENTRY = - §9#%d§r: §2%d§r Chunks Radius von der Mitte §2(%d, %d)§r aus in der Form §2%s§r
STATS_GENERATING = - Generiert: §2%s§r

@ -76,3 +76,9 @@ STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster
SAVING_CHUNKS = Saving %d loaded chunks...
CANCEL_FAIL = Failed to cancel task #%d in the given timeout!
NO_AUTOSTART = Autostart set to §2false§r. Pausing...
COMPLETED_TASKS_HEADER = §nCompleted Generation Tasks§r
COMPLETED_WORLD_HEADER = §l%s§r
COMPLETED_TASK_ENTRY = - §9#%d§r: §2%d§r chunks radius from center §2(%d, %d)§r with shape §2%s§r
STATS_GENERATING = - Generating: §2%s§r

@ -23,6 +23,7 @@ commands:
/<command> setCenter [[<world>] <chunkX> <chunkZ>]] - sets the center chunk of the world
/<command> getCenter [<world>] - returns the center chunk of the world
/<command> stats [<world>] - returns some chunk stats for the world or the whole server
/<command> completed - lists all completed tasks for all worlds
aliases:
- chm
- chunkm
@ -35,7 +36,7 @@ permissions:
description: Allows the list subcommand.
default: op
chunkmaster.cancel:
description: Allows the remove subcommand.
description: Allows the cancel subcommand.
default: op
chunkmaster.pause:
description: Allows the pause subcommand.
@ -55,6 +56,12 @@ permissions:
chunkmaster.getcenter:
description: Allows the getCenter subcommand.
default: op
chunkmaster.stats:
description: Allows the stats subcommand.
deault: op
chunkmaster.completed:
description: Allows the completed subcommand.
default: op
chunkmaster.chunkmaster:
description: Allows Chunkmaster commands.
default: op
@ -63,8 +70,14 @@ permissions:
default: op
children:
- chunkmaster.generate
- chunkmaster.listgentasks
- chunkmaster.removegentask
- chunkmaster.pausegentasks
- chunkmaster.resumegentasks
- chunkmaster.list
- chunkmaster.cancel
- chunkmaster.pause
- chunkmaster.resume
- chunkmaster.completed
- chunkmaster.tpchunk
- chunkmaster.reload
- chunkmaster.setcenter
- chunkmaster.getcenter
- chunkmaster.stats
- chunkmaster.chunkmaster

@ -0,0 +1,60 @@
package net.trivernis.chunkmaster.lib
import io.kotest.matchers.shouldBe
import org.junit.Test
class ArgParserTest {
var argParser = ArgParser()
@Test
fun `it parses arguments`() {
argParser.parseArguments("first second third forth").shouldBe(listOf("first", "second", "third", "forth"))
}
@Test
fun `it handles escaped sequences`() {
argParser.parseArguments("first second\\ pt2 third").shouldBe(listOf("first", "second pt2", "third"))
argParser.parseArguments("first \"second\\\" part 2\" third")
.shouldBe(listOf("first", "second\" part 2", "third"))
argParser.parseArguments("first \\\\second third").shouldBe(listOf("first", "\\second", "third"))
}
@Test
fun `it parses quoted arguments as one argument`() {
argParser.parseArguments("first \"second with space\" third")
.shouldBe(listOf("first", "second with space", "third"))
argParser.parseArguments("\"first\" \"second\" \"third\"").shouldBe(listOf("first", "second", "third"))
}
@Test
fun `it parses single arguments`() {
argParser.parseArguments("one").shouldBe(listOf("one"))
argParser.parseArguments("\"one\"").shouldBe(listOf("one"))
}
@Test
fun `it parses no arguments`() {
argParser.parseArguments("").shouldBe(emptyList())
}
@Test
fun `it parses just whitespace as no arguments`() {
argParser.parseArguments(" ").shouldBe(emptyList())
argParser.parseArguments("\t\t").shouldBe(emptyList())
}
@Test
fun `it parses arguments with weird whitespace`() {
argParser.parseArguments(" first second \t third \n forth ")
.shouldBe(listOf("first", "second", "third", "forth"))
}
@Test
fun `it deals predictable with malformed input`() {
argParser.parseArguments("first \"second third fourth").shouldBe(listOf("first", "second third fourth"))
argParser.parseArguments("\"first second \"third\" fourth")
.shouldBe(listOf("first second ", "third", " fourth"))
argParser.parseArguments("first second third fourth\"").shouldBe(listOf("first", "second", "third", "fourth"))
argParser.parseArguments("\"").shouldBe(emptyList())
}
}

@ -0,0 +1,29 @@
package net.trivernis.chunkmaster.lib
import io.kotest.matchers.string.shouldNotBeEmpty
import io.mockk.every
import io.mockk.mockk
import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.configuration.file.FileConfiguration
import org.junit.Test
class LanguageManagerTest {
private var langManager: LanguageManager
init {
val plugin = mockk<Chunkmaster>()
val config = mockk<FileConfiguration>()
every { plugin.dataFolder } returns createTempDir()
every { plugin.config } returns config
every { config.getString("language") } returns "en"
langManager = LanguageManager(plugin)
langManager.loadProperties()
}
@Test
fun `it returns localized for a key`() {
langManager.getLocalized("NOT_PAUSED").shouldNotBeEmpty()
}
}

@ -0,0 +1,75 @@
package net.trivernis.chunkmaster.lib.shapes
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.collections.shouldContainAll
import io.kotest.matchers.doubles.shouldBeBetween
import io.kotest.matchers.shouldBe
import org.junit.Test
import org.junit.jupiter.api.BeforeEach
class CircleTest {
private val circle = Circle(center = Pair(0, 0), radius = 2, start = Pair(0, 0))
@BeforeEach
fun init() {
circle.reset()
}
@Test
fun `it generates coordinates`() {
circle.next().shouldBe(Pair(0, 0))
circle.next().shouldBe(Pair(-1, -1))
circle.next().shouldBe(Pair(1, 0))
circle.next().shouldBe(Pair(-1, 0))
circle.next().shouldBe(Pair(1, -1))
circle.next().shouldBe(Pair(-1, 1))
circle.next().shouldBe(Pair(0, 1))
circle.next().shouldBe(Pair(0, -1))
circle.next().shouldBe(Pair(1, 1))
}
@Test
fun `it reports when reaching the end`() {
for (i in 1..25) {
circle.next()
}
circle.endReached().shouldBeTrue()
}
@Test
fun `it reports the radius`() {
for (i in 1..9) {
circle.next()
}
circle.currentRadius().shouldBe(1)
}
@Test
fun `it returns the right edges`() {
circle.getShapeEdgeLocations().shouldContainAll(
listOf(
Pair(2, -1),
Pair(2, 0),
Pair(2, 1),
Pair(1, 2),
Pair(0, 2),
Pair(-1, 2),
Pair(-2, 1),
Pair(-2, 0),
Pair(-2, -1),
Pair(-1, -2),
Pair(0, -2),
Pair(1, -2),
)
)
}
@Test
fun `it returns the progress`() {
circle.progress(2).shouldBe(0)
for (i in 1..7) {
circle.next()
}
circle.progress(2).shouldBeBetween(.5, .8, .0)
}
}

@ -0,0 +1,62 @@
package net.trivernis.chunkmaster.lib.shapes
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.collections.shouldContainAll
import io.kotest.matchers.shouldBe
import org.junit.Test
import org.junit.jupiter.api.BeforeEach
class SquareTest {
private val square = Square(center = Pair(0, 0), radius = 2, start = Pair(0, 0))
@BeforeEach
fun init() {
square.reset()
}
@Test
fun `it generates coordinates`() {
square.next().shouldBe(Pair(0, 0))
square.next().shouldBe(Pair(0, 1))
square.next().shouldBe(Pair(1, 1))
square.next().shouldBe(Pair(1, 0))
square.next().shouldBe(Pair(1, -1))
square.next().shouldBe(Pair(0, -1))
square.next().shouldBe(Pair(-1, -1))
square.next().shouldBe(Pair(-1, 0))
square.next().shouldBe(Pair(-1, 1))
square.next().shouldBe(Pair(-1, 2))
square.next().shouldBe(Pair(0, 2))
}
@Test
fun `it reports when reaching the end`() {
for (i in 1..25) {
square.next()
}
square.endReached().shouldBeTrue()
}
@Test
fun `it reports the radius`() {
for (i in 1..9) {
square.next()
}
square.currentRadius().shouldBe(1)
}
@Test
fun `it returns the right edges`() {
square.getShapeEdgeLocations().shouldContainAll(listOf(Pair(2, 2), Pair(-2, 2), Pair(2, -2), Pair(-2, -2)))
}
@Test
fun `it returns the progress`() {
square.progress(2).shouldBe(0)
for (i in 1..8) {
square.next()
}
square.progress(2).shouldBe(0.5)
}
}
Loading…
Cancel
Save