Compare commits

..

No commits in common. 'master' and 'v1.2.1' have entirely different histories.

@ -0,0 +1,13 @@
image: openjdk
matrix:
- env: SCRIPT=shadowJar
install:
- gradle dependencies
script:
- if [[ "SCRIPT" ]]; then gradle $SCRIPT; fi
cache:
- .gradle

@ -1,42 +0,0 @@
name: Gradle Build
on:
workflow_dispatch:
push:
branches: [ main, develop, actions ]
pull_request:
branches: [ main, develop, actions ]
jobs:
shadowJar:
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: Build Jar
run: chmod +x gradlew && ./gradlew shadowJar
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: chunkmaster
path: build/libs/chunkmaster-*.jar
- name: Cleanup Gradle Cache
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties

@ -1,36 +0,0 @@
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

2
.gitignore vendored

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

@ -1,8 +1,4 @@
# chunkmaster [![](https://circleci.com/gh/Trivernis/spigot-chunkmaster.svg?style=shield)](https://app.circleci.com/pipelines/github/Trivernis/spigot-chunkmaster) [![CodeFactor](https://www.codefactor.io/repository/github/trivernis/spigot-chunkmaster/badge)](https://www.codefactor.io/repository/github/trivernis/spigot-chunkmaster) [![](https://img.shields.io/discord/729250668162056313)](https://discord.gg/KZcMAgN) # chunkmaster ![](https://abstruse.trivernis.net/badge/1) ![](https://img.shields.io/discord/729250668162056313)
<div align="center">
<h1>This plugin isn't actively developed anymore. If you want to add features feel free to fork it and implement it yourself or use <a href="https://www.spigotmc.org/resources/chunky.81534/">Chunky</a> instead.</h1>
</div>
This plugin can be used to pre-generate the region of a world around the spawn chunk(s). 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)
@ -86,14 +82,32 @@ generation:
# The maximum amount of chunks that are loaded before unloading and saving them. # The maximum amount of chunks that are loaded before unloading and saving them.
# Higher values mean higher generation speed but greater memory usage. # Higher values mean higher generation speed but greater memory usage.
# The value should be a positive integer. # The value should be a positive integer.
max-loaded-chunks: 1000 max-loaded-chunks: 10
# Paper Only # Paper Only
# The maximum amount of requested chunks with the asynchronous paper chunk # The maximum amount of requested chunks with the asynchronous paper chunk
# loading method. Higher values mean faster generation but more memory usage and # loading method. Higher values mean faster generation but more memory usage
# bigger performance impact. Configuring it too hight might crash the server. # (and probably bigger performance impact).
# The value should be a positive integer.
max-pending-chunks: 10
# The period (in ticks) in which a generation step is run.
# Higher values mean less performance impact but slower generation.
# The value should be a positive integer.
period: 2
# The max amount of chunks that should be generated per step.
# Higher values mean higher generation speed but higher performance impact.
# The value should be a positive integer. # The value should be a positive integer.
max-pending-chunks: 500 chunks-per-step: 4
# Paper Only
# The number of already generated chunks that will be skipped for each step.
# Notice that these still have a performance impact because the server needs to check
# if the chunk is generated.
# Higher values mean faster generation but greater performance impact.
# The value should be a positive integer.
chunk-skips-per-step: 100
# The maximum milliseconds per tick the server is allowed to have # The maximum milliseconds per tick the server is allowed to have
# during the cunk generation process. # during the cunk generation process.
@ -101,25 +115,12 @@ generation:
# The value should be a positive integer greater than 50. # The value should be a positive integer greater than 50.
mspt-pause-threshold: 500 mspt-pause-threshold: 500
# The period in ticks for how often loaded chunks get unloaded.
# Unloading happens in the main thread and can impact the server performance.
# You can tweak this setting with the max-loaded-chunks setting to have either
# a lot of chunks unloaded at once or fewer chunks unloaded more often.
# If the maximum number of loaded chunks is reached the generation pauses until the
# unloading task runs again so keep that in mind.
# The value should be a positive integer.
unloading-period: 50
# Pauses the generation if the number of players on the server is larger or equal # Pauses the generation if the number of players on the server is larger or equal
# to the configured value # to the configured value
# Notice that playing on a server that constantly generates chunks can be # Notice that playing on a server that constantly generates chunks can be
# very laggy and can cause it to crash. # very laggy and can cause it to crash.
# The value should be a posivitve integer > 1. # The value should be a posivitve integer > 1.
pause-on-player-count: 1 pause-on-player-count: 1
# if the generation should automatically start on server startup
# the value should be a boolean
autostart: true
``` ```
### Spigot and Paper ### Spigot and Paper
@ -128,31 +129,9 @@ The plugin works on spigot and paper servers but is significantly faster on pape
(because it profits from asynchronous chunk loading an the better implementation of the (because it profits from asynchronous chunk loading an the better implementation of the
isChunkGenerated method). isChunkGenerated method).
## Translation
The **Mandarin** translation is provided by [NPBeta](https://github.com/NPBeta) and
was validated by [ed3d3d](https://twitter.com/ed3d3d).
The **French** translation is provided by [Corenb](https://github.com/Corenb) and
was validated by [Fiwel00](https://github.com/Fiwel00) and [Youssef Habri](https://github.com/youssefhabri).
The **German** and **English** translation is provided by me.
You can translate the plugin yourself and start a PR to this repository to add it to the
provided translation.
1. create an i18n folder in the plugins folder (plugins/Chunkmaster)
2. copy the [default translations file](https://github.com/Trivernis/spigot-chunkmaster/blob/master/src/main/resources/i18n/DEFAULT.i18n.properties)
into the newly created folder and rename it to <language-abbrevation>.i18n.properties
3. modify the values in the file for your translation (you can use minecraft § formatting sequences)
4. set the language property in the config file to your language abbrevation
5. start the plugin
Now you should see your translation being used by the plugin for localized messages.
## License ## License
This project is licensed under the GPLv3.0 License - see the This project is licensed under the GPLv3.0 License - see the [LICENSE](https://github.com/Trivernis/spigot-chunkmaster/blob/master/LICENSE) for details.
[LICENSE](https://github.com/Trivernis/spigot-chunkmaster/blob/master/LICENSE) for details.
## bStats ## bStats

@ -10,7 +10,7 @@ buildscript {
plugins { plugins {
id 'idea' id 'idea'
id 'org.jetbrains.kotlin.jvm' version '1.4.10' 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'
} }
@ -22,7 +22,8 @@ idea {
} }
group "net.trivernis" group "net.trivernis"
version PLUGIN_VERSION version "1.2.1"
sourceCompatibility = 1.8 sourceCompatibility = 1.8
repositories { repositories {
@ -44,28 +45,19 @@ repositories {
maven { maven {
name 'mikeprimm' name 'mikeprimm'
url 'https://repo.mikeprimm.com' url 'http://repo.mikeprimm.com'
}
maven {
url 'https://jitpack.io'
} }
} }
dependencies { dependencies {
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
compileOnly "com.destroystokyo.paper:paper-api:1.14.4-R0.1-SNAPSHOT" compileOnly "com.destroystokyo.paper:paper-api:1.14.4-R0.1-SNAPSHOT"
compileOnly "org.dynmap:dynmap-api:2.0" compileOnly "org.dynmap:dynmap-api:2.0"
compileOnly group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' compileOnly group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0'
compile "io.papermc:paperlib:1.0.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" compile "org.bstats:bstats-bukkit:1.5"
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" apply plugin: "com.github.johnrengelman.shadow"
@ -74,20 +66,10 @@ apply plugin: 'java'
shadowJar { shadowJar {
relocate 'io.papermc.lib', 'net.trivernis.chunkmaster.paperlib' relocate 'io.papermc.lib', 'net.trivernis.chunkmaster.paperlib'
relocate 'org.bstats', 'net.trivernis.chunkmaster.bstats' 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 { jar {
duplicatesStrategy = DuplicatesStrategy.INCLUDE from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
with copySpec {
from 'src/main/resources/'
include 'plugin.yml'
filter { String line ->
line.replace('$$PLUGIN_VERSION$$', PLUGIN_VERSION)
}
}
} }
compileKotlin { compileKotlin {

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

Binary file not shown.

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored

@ -1,185 +0,0 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored

@ -1,89 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -3,14 +3,15 @@ package net.trivernis.chunkmaster
import io.papermc.lib.PaperLib import io.papermc.lib.PaperLib
import net.trivernis.chunkmaster.commands.CommandChunkmaster import net.trivernis.chunkmaster.commands.CommandChunkmaster
import net.trivernis.chunkmaster.lib.LanguageManager import net.trivernis.chunkmaster.lib.LanguageManager
import net.trivernis.chunkmaster.lib.database.SqliteManager import net.trivernis.chunkmaster.lib.SqliteManager
import net.trivernis.chunkmaster.lib.generation.GenerationManager import net.trivernis.chunkmaster.lib.generation.GenerationManager
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 org.dynmap.DynmapAPI import org.dynmap.DynmapAPI
import java.util.logging.Level
open class Chunkmaster : JavaPlugin() { class Chunkmaster: JavaPlugin() {
lateinit var sqliteManager: SqliteManager lateinit var sqliteManager: SqliteManager
lateinit var generationManager: GenerationManager lateinit var generationManager: GenerationManager
lateinit var langManager: LanguageManager lateinit var langManager: LanguageManager
@ -30,7 +31,7 @@ open class Chunkmaster : JavaPlugin() {
logger.finest("LogLevel: FINEST") logger.finest("LogLevel: FINEST")
configure() configure()
Metrics(this) val metrics = Metrics(this)
langManager = LanguageManager(this) langManager = LanguageManager(this)
langManager.loadProperties() langManager.loadProperties()
@ -48,7 +49,7 @@ open class Chunkmaster : JavaPlugin() {
if (PaperLib.isPaper() && PaperLib.getMinecraftPatchVersion() >= 225) { if (PaperLib.isPaper() && PaperLib.getMinecraftPatchVersion() >= 225) {
tpsTask = server.scheduler.runTaskTimer(this, Runnable { tpsTask = server.scheduler.runTaskTimer(this, Runnable {
mspt = 1000 / server.currentTick // use papers exposed tick rather than calculating it mspt = 1000/server.currentTick // use papers exposed tick rather than calculating it
}, 1, 300) }, 1, 300)
} else { } else {
tpsTask = server.scheduler.runTaskTimer(this, Runnable { tpsTask = server.scheduler.runTaskTimer(this, Runnable {
@ -66,7 +67,6 @@ open class Chunkmaster : JavaPlugin() {
override fun onDisable() { override fun onDisable() {
logger.info(langManager.getLocalized("STOPPING_ALL_TASKS")) logger.info(langManager.getLocalized("STOPPING_ALL_TASKS"))
generationManager.stopAll() generationManager.stopAll()
server.scheduler.cancelTasks(this)
} }
/** /**
@ -74,13 +74,14 @@ open class Chunkmaster : JavaPlugin() {
*/ */
private fun configure() { private fun configure() {
dataFolder.mkdir() dataFolder.mkdir()
config.addDefault("generation.period", 2L)
config.addDefault("generation.chunks-per-step", 2)
config.addDefault("generation.chunk-skips-per-step", 100)
config.addDefault("generation.mspt-pause-threshold", 500L) config.addDefault("generation.mspt-pause-threshold", 500L)
config.addDefault("generation.pause-on-player-count", 1) config.addDefault("generation.pause-on-player-count", 1)
config.addDefault("generation.max-pending-chunks", 500) config.addDefault("generation.max-pending-chunks", 10)
config.addDefault("generation.max-loaded-chunks", 1000) config.addDefault("generation.max-loaded-chunks", 10)
config.addDefault("generation.unloading-period", 50L)
config.addDefault("generation.ignore-worldborder", false) config.addDefault("generation.ignore-worldborder", false)
config.addDefault("generation.autostart", true)
config.addDefault("database.filename", "chunkmaster.db") config.addDefault("database.filename", "chunkmaster.db")
config.addDefault("language", "en") config.addDefault("language", "en")
config.addDefault("dynmap", true) config.addDefault("dynmap", true)
@ -94,25 +95,21 @@ open class Chunkmaster : JavaPlugin() {
private fun initDatabase() { private fun initDatabase() {
logger.info(langManager.getLocalized("DB_INIT")) logger.info(langManager.getLocalized("DB_INIT"))
try { try {
this.sqliteManager = SqliteManager(this) this.sqliteManager = SqliteManager( this)
sqliteManager.init() sqliteManager.init()
logger.info(langManager.getLocalized("DB_INIT_FINISHED")) logger.info(langManager.getLocalized("DB_INIT_FINISHED"))
} catch (e: Exception) { } catch(e: Exception) {
logger.warning(langManager.getLocalized("DB_INIT_EROR", e.message!!)) logger.warning(langManager.getLocalized("DB_INIT_EROR", e.message!!))
} }
} }
private fun getDynmap(): DynmapAPI? { private fun getDynmap(): DynmapAPI? {
return try {
val dynmap = server.pluginManager.getPlugin("dynmap") val dynmap = server.pluginManager.getPlugin("dynmap")
if (dynmap != null && dynmap is DynmapAPI) { return if (dynmap != null && dynmap is DynmapAPI) {
logger.info(langManager.getLocalized("PLUGIN_DETECTED", "dynmap", dynmap.dynmapVersion)) logger.info(langManager.getLocalized("PLUGIN_DETECTED", "dynmap", dynmap.dynmapVersion))
dynmap dynmap
} else { } else {
null null
} }
} catch (e: IllegalStateException) {
null
}
} }
} }

@ -9,7 +9,7 @@ import org.bukkit.event.player.PlayerQuitEvent
class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server) : Listener { class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server: Server) : Listener {
private val pauseOnPlayerCount: Int private val pauseOnPlayerCount: Int
get() { get () {
return chunkmaster.config.getInt("generation.pause-on-player-count") return chunkmaster.config.getInt("generation.pause-on-player-count")
} }
private var playerPaused = false private var playerPaused = false
@ -25,7 +25,7 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_PLAYER_LEAVE")) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_PLAYER_LEAVE"))
} }
chunkmaster.generationManager.resumeAll() chunkmaster.generationManager.resumeAll()
} else if (chunkmaster.generationManager.paused) { } else if (chunkmaster.generationManager.paused){
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("PAUSE_MANUALLY")) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("PAUSE_MANUALLY"))
playerPaused = chunkmaster.generationManager.paused playerPaused = chunkmaster.generationManager.paused
} }
@ -41,10 +41,8 @@ class ChunkmasterEvents(private val chunkmaster: Chunkmaster, private val server
if (chunkmaster.generationManager.tasks.isNotEmpty()) { if (chunkmaster.generationManager.tasks.isNotEmpty()) {
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("PAUSE_PLAYER_JOIN")) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("PAUSE_PLAYER_JOIN"))
} }
if (!chunkmaster.generationManager.paused) {
playerPaused = chunkmaster.generationManager.paused playerPaused = chunkmaster.generationManager.paused
chunkmaster.generationManager.pauseAll() chunkmaster.generationManager.pauseAll()
} }
} }
}
} }

@ -1,11 +1,14 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand import net.trivernis.chunkmaster.lib.Subcommand
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"
/** /**
@ -19,7 +22,7 @@ class CmdCancel(private val chunkmaster: Chunkmaster) : Subcommand {
): 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()
} }
@ -36,7 +39,7 @@ class CmdCancel(private val chunkmaster: Chunkmaster) : Subcommand {
} }
if (index != null && chunkmaster.generationManager.removeTask(index)) { if (index != null && chunkmaster.generationManager.removeTask(index)) {
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_CANCELLED", index)) sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_CANCELED", index))
true true
} else { } else {
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_NOT_FOUND", args[0])) sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_NOT_FOUND", args[0]))
@ -44,7 +47,7 @@ class CmdCancel(private val chunkmaster: Chunkmaster) : Subcommand {
} }
} else { } else {
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ID_REQUIRED")) sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ID_REQUIRED"));
false false
} }
} }

@ -1,44 +0,0 @@
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()
}
}

@ -6,7 +6,7 @@ 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 CmdGenerate(private val chunkmaster: Chunkmaster) : Subcommand { class CmdGenerate(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "generate" override val name = "generate"
/** /**
@ -20,14 +20,14 @@ class CmdGenerate(private val chunkmaster: Chunkmaster) : Subcommand {
): 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 shapes.filter { it.indexOf(args[1]) == 0 }.toMutableList() return shapes.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 shapes.filter { it.indexOf(args[2]) == 0 }.toMutableList() return shapes.filter {it.indexOf(args[2]) == 0}.toMutableList()
} }
} }
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
@ -97,23 +97,20 @@ class CmdGenerate(private val chunkmaster: Chunkmaster) : Subcommand {
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, if (blockRadius > 0) blockRadius / 16 else -1, shape) chunkmaster.generationManager.addTask(world, if (blockRadius > 0) blockRadius/16 else -1 , shape)
sender.sendMessage( sender.sendMessage(chunkmaster.langManager
chunkmaster.langManager .getLocalized("TASK_CREATION_SUCCESS",
.getLocalized(
"TASK_CREATION_SUCCESS",
worldName, worldName,
if (blockRadius > 0) { if (blockRadius > 0) {
chunkmaster.langManager.getLocalized("TASK_UNIT_RADIUS", blockRadius) chunkmaster.langManager.getLocalized("TASK_UNIT_RADIUS", blockRadius)
} else { } else{
chunkmaster.langManager.getLocalized("TASK_UNIT_WORLDBORDER") chunkmaster.langManager.getLocalized("TASK_UNIT_WORLDBORDER")
}, },
shape shape
) ))
)
true true
} else if (world == null) { } else if (world == null){
sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName)) sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName));
false false
} else { } else {
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ALREADY_EXISTS", worldName)) sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ALREADY_EXISTS", worldName))

@ -6,8 +6,8 @@ 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 CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand { class CmdGetCenter(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "getCenter" override val name = "getCenter";
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
@ -17,7 +17,7 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
): 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()
} }
return emptyList<String>().toMutableList() return emptyList<String>().toMutableList()
} }
@ -27,7 +27,7 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
if (args.isNotEmpty()) { if (args.isNotEmpty()) {
args[0] args[0]
} else { } else {
sender.world.name sender.world.name;
} }
} else { } else {
if (args.isEmpty()) { if (args.isEmpty()) {
@ -37,6 +37,12 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
args[0] args[0]
} }
} }
if (chunkmaster.generationManager.worldCenters.isEmpty()) {
chunkmaster.generationManager.loadWorldCenters() {
sendCenterInfo(sender, worldName)
}
return true
}
sendCenterInfo(sender, worldName) sendCenterInfo(sender, worldName)
return true return true
} }
@ -45,24 +51,15 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
* Sends the center information * Sends the center information
*/ */
private fun sendCenterInfo(sender: CommandSender, worldName: String) { private fun sendCenterInfo(sender: CommandSender, worldName: String) {
chunkmaster.generationManager.worldProperties.getWorldCenter(worldName).thenAccept { worldCenter -> var center = chunkmaster.generationManager.worldCenters[worldName]
var center = worldCenter
if (center == null) { if (center == null) {
val world = sender.server.worlds.find { it.name == worldName } val world = sender.server.worlds.find { it.name == worldName }
if (world == null) { if (world == null) {
sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName)) sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName))
return@thenAccept return
} }
center = Pair(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) center = Pair(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z)
} }
sender.sendMessage( sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_INFO", worldName, center.first, center.second))
chunkmaster.langManager.getLocalized(
"CENTER_INFO",
worldName,
center.first,
center.second
)
)
}
} }
} }

@ -2,12 +2,11 @@ package net.trivernis.chunkmaster.commands
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.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
import kotlin.math.ceil
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(
@ -49,18 +48,11 @@ class CmdList(private val chunkmaster: Chunkmaster) : Subcommand {
*/ */
private fun getGenerationEntry(task: TaskEntry): String { private fun getGenerationEntry(task: TaskEntry): String {
val genTask = task.generationTask val genTask = task.generationTask
val progress = val percentage = if (genTask.radius > 0)
genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null) " (%.1f".format(genTask.shape.progress()*100) + "%)."
val percentage = " (%.1f".format(progress * 100) + "%)." else
""
val count = if (genTask.radius > 0) { return "\n" + chunkmaster.langManager.getLocalized("TASKS_ENTRY",
"${genTask.count} / ${ceil(genTask.shape.total()).toInt()}" task.id, genTask.world.name, genTask.count, percentage)
} else {
"${genTask.count} / worldborder"
}
return "\n" + chunkmaster.langManager.getLocalized(
"TASKS_ENTRY",
task.id, genTask.world.name, genTask.state.toString(), count, percentage
)
} }
} }

@ -1,5 +1,7 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.Subcommand import net.trivernis.chunkmaster.lib.Subcommand
import org.bukkit.command.Command import org.bukkit.command.Command

@ -1,11 +1,13 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.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(

@ -1,11 +1,13 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.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(

@ -6,8 +6,8 @@ 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 CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand { class CmdSetCenter(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "setCenter" override val name = "setCenter";
override fun onTabComplete( override fun onTabComplete(
sender: CommandSender, sender: CommandSender,
@ -18,10 +18,10 @@ class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
if (args.size == 1) { if (args.size == 1) {
if (args[0].toIntOrNull() == null) { if (args[0].toIntOrNull() == null) {
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()
} }
} }
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 {
@ -36,11 +36,6 @@ class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
centerX = sender.location.chunk.x centerX = sender.location.chunk.x
centerZ = sender.location.chunk.z centerZ = sender.location.chunk.z
} }
args.size == 1 -> {
world = args[0]
centerX = sender.location.chunk.x
centerZ = sender.location.chunk.z
}
args.size == 2 -> { args.size == 2 -> {
world = sender.world.name world = sender.world.name
if (args[0].toIntOrNull() == null || args[1].toIntOrNull() == null) { if (args[0].toIntOrNull() == null || args[1].toIntOrNull() == null) {
@ -72,7 +67,7 @@ class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
centerZ = args[2].toInt() centerZ = args[2].toInt()
} }
} }
chunkmaster.generationManager.worldProperties.setWorldCenter(world, Pair(centerX, centerZ)) chunkmaster.generationManager.updateWorldCenter(world, Pair(centerX, centerZ))
sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_UPDATED", world, centerX, centerZ)) sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_UPDATED", world, centerX, centerZ))
return true return true
} }

@ -5,8 +5,9 @@ import net.trivernis.chunkmaster.lib.Subcommand
import org.bukkit.World import org.bukkit.World
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
class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand { class CmdStats(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "stats" override val name = "stats"
override fun onTabComplete( override fun onTabComplete(
@ -22,10 +23,8 @@ class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand {
if (args.isNotEmpty()) { if (args.isNotEmpty()) {
val world = sender.server.getWorld(args[0]) val world = sender.server.getWorld(args[0])
if (world == null) { if (world == null) {
sender.sendMessage( sender.sendMessage(chunkmaster.langManager.getLocalized("STATS_HEADER") + "\n" +
chunkmaster.langManager.getLocalized("STATS_HEADER") + "\n" + chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", args[0]))
chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", args[0])
)
return false return false
} }
sender.sendMessage(getWorldStatsMessage(sender, world)) sender.sendMessage(getWorldStatsMessage(sender, world))
@ -37,16 +36,16 @@ class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand {
} }
private fun getWorldStatsMessage(sender: CommandSender, world: World): String { private fun getWorldStatsMessage(sender: CommandSender, world: World): String {
return """ var message = """
${chunkmaster.langManager.getLocalized("STATS_WORLD_NAME", world.name)} ${chunkmaster.langManager.getLocalized("STATS_WORLD_NAME", world.name)}
${chunkmaster.langManager.getLocalized("STATS_ENTITY_COUNT", world.entities.size)} ${chunkmaster.langManager.getLocalized("STATS_ENTITY_COUNT", world.entities.size)}
${chunkmaster.langManager.getLocalized("STATS_LOADED_CHUNKS", world.loadedChunks.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() """.trimIndent()
val task = chunkmaster.generationManager.tasks.find { it.generationTask.world == world }
if (task != null) {
message += "\n" + chunkmaster.langManager.getLocalized("STATS_PLUGIN_LOADED_CHUNKS", task.generationTask.loadedChunksCount)
}
return message
} }
private fun getServerStatsMessage(sender: CommandSender): String { private fun getServerStatsMessage(sender: CommandSender): String {
@ -58,22 +57,8 @@ class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand {
${chunkmaster.langManager.getLocalized("STATS_SERVER")} ${chunkmaster.langManager.getLocalized("STATS_SERVER")}
${chunkmaster.langManager.getLocalized("STATS_SERVER_VERSION", sender.server.version)} ${chunkmaster.langManager.getLocalized("STATS_SERVER_VERSION", sender.server.version)}
${chunkmaster.langManager.getLocalized("STATS_PLUGIN_VERSION", chunkmaster.description.version)} ${chunkmaster.langManager.getLocalized("STATS_PLUGIN_VERSION", chunkmaster.description.version)}
${ ${chunkmaster.langManager.getLocalized("STATS_MEMORY", memUsed/1000000, runtime.maxMemory()/1000000, (memUsed.toFloat()/runtime.maxMemory().toFloat()) * 100)}
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_CORES", runtime.availableProcessors())}
${
chunkmaster.langManager.getLocalized(
"STATS_PLUGIN_LOADED_CHUNKS",
chunkmaster.generationManager.loadedChunkCount
)
}
""".trimIndent() """.trimIndent()
for (world in sender.server.worlds) { for (world in sender.server.worlds) {
message += "\n\n" + getWorldStatsMessage(sender, world) message += "\n\n" + getWorldStatsMessage(sender, world)

@ -1,6 +1,8 @@
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.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.Material import org.bukkit.Material
@ -8,7 +10,7 @@ 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(private val chunkmaster: Chunkmaster) : Subcommand { class CmdTpChunk(private val chunkmaster: Chunkmaster): Subcommand {
override val name = "tpchunk" override val name = "tpchunk"
override fun onTabComplete( override fun onTabComplete(

@ -1,7 +1,8 @@
package net.trivernis.chunkmaster.commands package net.trivernis.chunkmaster.commands
import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ComponentBuilder
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.ArgParser
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
@ -12,7 +13,6 @@ 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>()
private val argParser = ArgParser()
init { init {
registerCommands() registerCommands()
@ -38,14 +38,7 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
/** /**
* /chunkmaster command to handle all commands * /chunkmaster command to handle all commands
*/ */
override fun onCommand( override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
sender: CommandSender,
command: Command,
label: String,
bukkitArgs: Array<out String>
): Boolean {
val args = argParser.parseArguments(bukkitArgs.joinToString(" "))
if (args.isNotEmpty()) { if (args.isNotEmpty()) {
if (sender.hasPermission("chunkmaster.${args[0].toLowerCase()}")) { if (sender.hasPermission("chunkmaster.${args[0].toLowerCase()}")) {
return if (commands.containsKey(args[0])) { return if (commands.containsKey(args[0])) {
@ -96,8 +89,5 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
val cmdStats = CmdStats(chunkmaster) val cmdStats = CmdStats(chunkmaster)
commands[cmdStats.name] = cmdStats commands[cmdStats.name] = cmdStats
val cmdCompleted = CmdCompleted(chunkmaster)
commands[cmdCompleted.name] = cmdCompleted
} }
} }

@ -1,92 +0,0 @@
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
}
}

@ -1,8 +1,8 @@
package net.trivernis.chunkmaster.lib package net.trivernis.chunkmaster.lib
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import java.lang.Exception
import java.util.Properties
import java.io.* import java.io.*
import java.util.*
class LanguageManager(private val plugin: Chunkmaster) { class LanguageManager(private val plugin: Chunkmaster) {
private val langProps = Properties() private val langProps = Properties()
@ -20,14 +20,12 @@ class LanguageManager(private val plugin: Chunkmaster) {
val file = File(langFile) val file = File(langFile)
val loader = Thread.currentThread().contextClassLoader val loader = Thread.currentThread().contextClassLoader
val defaultStream = this.javaClass.getResourceAsStream("/i18n/DEFAULT.i18n.properties") val defaultStream = this.javaClass.getResourceAsStream("/i18n/DEFAULT.i18n.properties")
if (defaultStream != null) { if (defaultStream != null) {
langProps.load(getReaderForProperties(defaultStream)) langProps.load(getReaderForProperties(defaultStream))
defaultStream.close() defaultStream.close()
} else { } else {
plugin.logger.severe("Couldn't load default language properties.") plugin.logger.severe("Couldn't load default language properties.")
} }
if (file.exists()) { if (file.exists()) {
try { try {
val inputStream = loader.getResourceAsStream(langFile) val inputStream = loader.getResourceAsStream(langFile)
@ -56,13 +54,8 @@ class LanguageManager(private val plugin: Chunkmaster) {
* Returns a localized message with replacements * Returns a localized message with replacements
*/ */
fun getLocalized(key: String, vararg replacements: Any): String { fun getLocalized(key: String, vararg replacements: Any): String {
try {
val localizedString = langProps.getProperty(key) val localizedString = langProps.getProperty(key)
return String.format(localizedString, *replacements) return String.format(localizedString, *replacements)
} catch (e: NullPointerException) {
plugin.logger.severe("Failed to get localized entry for $key")
throw e
}
} }
/** /**

@ -1,9 +1,12 @@
package net.trivernis.chunkmaster.lib.database package net.trivernis.chunkmaster.lib
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import org.apache.commons.lang.exception.ExceptionUtils import org.apache.commons.lang.exception.ExceptionUtils
import org.sqlite.SQLiteConnection
import java.lang.Exception
import java.sql.Connection import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import java.sql.PreparedStatement
import java.sql.ResultSet import java.sql.ResultSet
class SqliteManager(private val chunkmaster: Chunkmaster) { class SqliteManager(private val chunkmaster: Chunkmaster) {
@ -18,8 +21,7 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
Pair("last_z", "integer NOT NULL DEFAULT 0"), Pair("last_z", "integer NOT NULL DEFAULT 0"),
Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"), Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"),
Pair("radius", "integer DEFAULT -1"), Pair("radius", "integer DEFAULT -1"),
Pair("shape", "text NOT NULL DEFAULT 'square'"), Pair("shape", "text NOT NULL DEFAULT 'square'")
Pair("state", "text NOT NULL DEFAULT 'GENERATING'")
) )
), ),
Pair( Pair(
@ -29,26 +31,6 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
Pair("center_x", "integer NOT NULL DEFAULT 0"), Pair("center_x", "integer NOT NULL DEFAULT 0"),
Pair("center_z", "integer NOT NULL DEFAULT 0") Pair("center_z", "integer NOT NULL DEFAULT 0")
) )
),
Pair(
"pending_chunks",
listOf(
Pair("id", "integer PRIMARY KEY AUTOINCREMENT"),
Pair("task_id", "integer NOT NULL"),
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>>>() private val needUpdate = HashSet<Pair<String, Pair<String, String>>>()
@ -56,11 +38,6 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
private var connection: Connection? = null private var connection: Connection? = null
private var activeTasks = 0 private var activeTasks = 0
val worldProperties = WorldProperties(this)
val pendingChunks = PendingChunks(this)
val generationTasks = GenerationTasks(this)
val completedGenerationTasks = CompletedGenerationTasks(this)
/** /**
* Returns the connection to the database * Returns the connection to the database
*/ */
@ -70,10 +47,8 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
} }
try { try {
Class.forName("org.sqlite.JDBC") Class.forName("org.sqlite.JDBC")
this.connection = DriverManager.getConnection( this.connection = DriverManager.getConnection("jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" +
"jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + "${chunkmaster.config.getString("database.filename")}")
"${chunkmaster.config.getString("database.filename")}"
)
return this.connection return this.connection
} catch (e: Exception) { } catch (e: Exception) {
chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR")) chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR"))
@ -117,18 +92,17 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
/** /**
* Executes a sql statement on the database. * Executes a sql statement on the database.
*/ */
fun executeStatement(sql: String, values: HashMap<Int, Any>, callback: ((ResultSet?) -> Unit)?) { fun executeStatement(sql: String, values: HashMap<Int, Any>, callback: ((ResultSet) -> Unit)?) {
val connection = getConnection() val connection = getConnection()
activeTasks++ activeTasks++
if (connection != null) { if (connection != null) {
try { try {
//println("'$sql' with values $values")
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
for (parameterValue in values) { for (parameterValue in values) {
statement.setObject(parameterValue.key, parameterValue.value) statement.setObject(parameterValue.key, parameterValue.value)
} }
statement.execute() statement.execute()
val res: ResultSet? = statement.resultSet val res = statement.resultSet
if (callback != null) { if (callback != null) {
callback(res) callback(res)
} }
@ -156,17 +130,11 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
try { try {
var tableDef = "CREATE TABLE IF NOT EXISTS $table (" var tableDef = "CREATE TABLE IF NOT EXISTS $table ("
for (column in tables.find { it.first == table }!!.second) { for (column in tables.find{it.first == table}!!.second) {
tableDef += "${column.first} ${column.second}," tableDef += "${column.first} ${column.second},"
} }
tableDef = tableDef.substringBeforeLast(",") + ");" tableDef = tableDef.substringBeforeLast(",") + ");"
chunkmaster.logger.finest( chunkmaster.logger.finest(chunkmaster.langManager.getLocalized("CREATE_TABLE_DEFINITION", table, tableDef))
chunkmaster.langManager.getLocalized(
"CREATE_TABLE_DEFINITION",
table,
tableDef
)
)
executeStatement(tableDef, HashMap(), null) executeStatement(tableDef, HashMap(), null)
} catch (e: Exception) { } catch (e: Exception) {
chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TABLE_CREATE_ERROR", table)) chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TABLE_CREATE_ERROR", table))
@ -178,21 +146,9 @@ class SqliteManager(private val chunkmaster: Chunkmaster) {
val updateSql = "ALTER TABLE ${table.first} ADD COLUMN ${table.second.first} ${table.second.second}" val updateSql = "ALTER TABLE ${table.first} ADD COLUMN ${table.second.first} ${table.second.second}"
try { try {
executeStatement(updateSql, HashMap(), null) executeStatement(updateSql, HashMap(), null)
chunkmaster.logger.finest( chunkmaster.logger.finest(chunkmaster.langManager.getLocalized("UPDATE_TABLE_DEFINITION", table.first, updateSql))
chunkmaster.langManager.getLocalized(
"UPDATE_TABLE_DEFINITION",
table.first,
updateSql
)
)
} catch (e: Exception) { } catch (e: Exception) {
chunkmaster.logger.severe( chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("UPDATE_TABLE_FAILED", table.first, updateSql))
chunkmaster.langManager.getLocalized(
"UPDATE_TABLE_FAILED",
table.first,
updateSql
)
)
chunkmaster.logger.severe(e.message) chunkmaster.logger.severe(e.message)
chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) chunkmaster.logger.info(ExceptionUtils.getStackTrace(e))
} }

@ -1,11 +0,0 @@
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
)

@ -1,80 +0,0 @@
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
}
}

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

@ -1,104 +0,0 @@
package net.trivernis.chunkmaster.lib.database
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
import net.trivernis.chunkmaster.lib.generation.TaskState
import java.util.concurrent.CompletableFuture
class GenerationTasks(private val sqliteManager: SqliteManager) {
/**
* Returns all stored generation tasks
*/
fun getGenerationTasks(): CompletableFuture<List<GenerationTaskData>> {
val completableFuture = CompletableFuture<List<GenerationTaskData>>()
sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) { res ->
val tasks = ArrayList<GenerationTaskData>()
while (res!!.next()) {
val id = res.getInt("id")
val world = res.getString("world")
val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z"))
val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z"))
val radius = res.getInt("radius")
val shape = res.getString("shape")
val state = stringToState(res.getString("state"))
val taskData = GenerationTaskData(id, world, radius, shape, state, center, last)
if (tasks.find { it.id == id } == null) {
tasks.add(taskData)
}
}
completableFuture.complete(tasks)
}
return completableFuture
}
/**
* Adds a generation task to the database
*/
fun addGenerationTask(world: String, center: ChunkCoordinates, radius: Int, shape: String): CompletableFuture<Int> {
val completableFuture = CompletableFuture<Int>()
sqliteManager.executeStatement(
"""
INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, radius, shape)
values (?, ?, ?, ?, ?, ?, ?)""",
hashMapOf(
1 to center.x,
2 to center.z,
3 to center.x,
4 to center.z,
5 to world,
6 to radius,
7 to shape
)
) {
sqliteManager.executeStatement(
"""
SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1
""".trimIndent(), HashMap()
) {
it!!.next()
completableFuture.complete(it.getInt("id"))
}
}
return completableFuture
}
/**
* Deletes a generationTask from the database
*/
fun deleteGenerationTask(id: Int): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
sqliteManager.executeStatement("DELETE FROM generation_tasks WHERE id = ?;", hashMapOf(1 to id)) {
completableFuture.complete(null)
}
return completableFuture
}
fun updateGenerationTask(id: Int, last: ChunkCoordinates, state: TaskState): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
sqliteManager.executeStatement(
"""
UPDATE generation_tasks SET last_x = ?, last_z = ?, state = ?
WHERE id = ?
""".trimIndent(),
hashMapOf(1 to last.x, 2 to last.z, 3 to state.toString(), 4 to id)
) {
completableFuture.complete(null)
}
return completableFuture
}
/**
* Converts a string into a task state
*/
private fun stringToState(stringState: String): TaskState {
TaskState.valueOf(stringState)
return when (stringState) {
"GENERATING" -> TaskState.GENERATING
"VALIDATING" -> TaskState.VALIDATING
"PAUSING" -> TaskState.PAUSING
"CORRECTING" -> TaskState.CORRECTING
else -> TaskState.GENERATING
}
}
}

@ -1,84 +0,0 @@
package net.trivernis.chunkmaster.lib.database
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
import java.util.concurrent.CompletableFuture
import kotlin.math.ceil
class PendingChunks(private val sqliteManager: SqliteManager) {
private val insertionCount = 300
/**
* Returns a list of pending chunks for a taskId
*/
fun getPendingChunks(taskId: Int): CompletableFuture<List<ChunkCoordinates>> {
val completableFuture = CompletableFuture<List<ChunkCoordinates>>()
sqliteManager.executeStatement("SELECT * FROM pending_chunks WHERE task_id = ?", hashMapOf(1 to taskId)) {
val pendingChunks = ArrayList<ChunkCoordinates>()
while (it!!.next()) {
pendingChunks.add(ChunkCoordinates(it.getInt("chunk_x"), it.getInt("chunk_z")))
}
completableFuture.complete(pendingChunks)
}
return completableFuture
}
/**
* Clears all pending chunks of a task
*/
fun clearPendingChunks(taskId: Int): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
sqliteManager.executeStatement("DELETE FROM pending_chunks WHERE task_id = ?", hashMapOf(1 to taskId)) {
completableFuture.complete(null)
}
return completableFuture
}
fun addPendingChunks(taskId: Int, pendingChunks: List<ChunkCoordinates>): CompletableFuture<Void> {
val futures = ArrayList<CompletableFuture<Void>>()
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)
)
)
)
}
if (futures.size > 0) {
return CompletableFuture.allOf(*futures.toTypedArray())
} else {
return CompletableFuture.supplyAsync { null }
}
}
/**
* Adds pending chunks for a taskid
*/
private fun insertPendingChunks(taskId: Int, pendingChunks: List<ChunkCoordinates>): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
if (pendingChunks.isEmpty()) {
completableFuture.complete(null)
} else {
var sql = "INSERT INTO pending_chunks (task_id, chunk_x, chunk_z) VALUES"
var index = 1
val valueMap = HashMap<Int, Any>()
for (coordinates in pendingChunks) {
sql += "(?, ?, ?),"
valueMap[index++] = taskId
valueMap[index++] = coordinates.x
valueMap[index++] = coordinates.z
}
sqliteManager.executeStatement(sql.removeSuffix(","), valueMap) {
completableFuture.complete(null)
}
}
return completableFuture
}
}

@ -1,83 +0,0 @@
package net.trivernis.chunkmaster.lib.database
import java.util.concurrent.CompletableFuture
class WorldProperties(private val sqliteManager: SqliteManager) {
private val properties = HashMap<String, Pair<Int, Int>>()
/**
* Returns the world center for one world
*/
fun getWorldCenter(worldName: String): CompletableFuture<Pair<Int, Int>?> {
val completableFuture = CompletableFuture<Pair<Int, Int>?>()
if (properties[worldName] != null) {
completableFuture.complete(properties[worldName])
} else {
sqliteManager.executeStatement("SELECT * FROM world_properties WHERE name = ?", hashMapOf(1 to worldName)) {
if (it != null && it.next()) {
completableFuture.complete(Pair(it.getInt("center_x"), it.getInt("center_z")))
} else {
completableFuture.complete(null)
}
}
}
return completableFuture
}
/**
* Updates the center of a world
*/
fun setWorldCenter(worldName: String, center: Pair<Int, Int>): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
getWorldCenter(worldName).thenAccept {
if (it != null) {
updateWorldProperties(worldName, center).thenAccept { completableFuture.complete(null) }
} else {
insertWorldProperties(worldName, center).thenAccept { completableFuture.complete(null) }
}
}
return completableFuture
}
/**
* Updates an entry in the world properties
*/
private fun updateWorldProperties(worldName: String, center: Pair<Int, Int>): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
sqliteManager.executeStatement(
"UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?",
hashMapOf(
1 to center.first,
2 to center.second,
3 to worldName
)
) {
properties[worldName] = center
completableFuture.complete(null)
}
return completableFuture
}
/**
* Inserts into the world properties
*/
private fun insertWorldProperties(worldName: String, center: Pair<Int, Int>): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
sqliteManager.executeStatement(
"INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)",
hashMapOf(
1 to worldName,
2 to center.first,
3 to center.second
)
) {
properties[worldName] = center
completableFuture.complete(null)
}
return completableFuture
}
}

@ -2,6 +2,7 @@ package net.trivernis.chunkmaster.lib.dynmap
import org.bukkit.Location import org.bukkit.Location
import org.dynmap.markers.AreaMarker import org.dynmap.markers.AreaMarker
import org.dynmap.markers.Marker
import org.dynmap.markers.MarkerSet import org.dynmap.markers.MarkerSet
import org.dynmap.markers.PolyLineMarker import org.dynmap.markers.PolyLineMarker
@ -45,12 +46,7 @@ class ExtendedMarkerSet(private val markerSet: MarkerSet) {
} }
fun creUpdatePolyLineMarker( fun creUpdatePolyLineMarker(id: String, label: String, edges: List<Location>, style: MarkerStyle?): PolyLineMarker? {
id: String,
label: String,
edges: List<Location>,
style: MarkerStyle?
): PolyLineMarker? {
var marker = markerSet.findPolyLineMarker(id) var marker = markerSet.findPolyLineMarker(id)
val xList = edges.map { it.x } val xList = edges.map { it.x }
val yList = edges.map { it.y } val yList = edges.map { it.y }
@ -58,16 +54,7 @@ class ExtendedMarkerSet(private val markerSet: MarkerSet) {
if (marker != null) { if (marker != null) {
marker.setCornerLocations(xList.toDoubleArray(), yList.toDoubleArray(), zList.toDoubleArray()) marker.setCornerLocations(xList.toDoubleArray(), yList.toDoubleArray(), zList.toDoubleArray())
} else { } else {
marker = markerSet.createPolyLineMarker( marker = markerSet.createPolyLineMarker(id, label, false, edges.first().world.name, xList.toDoubleArray(), yList.toDoubleArray(), zList.toDoubleArray(), true)
id,
label,
false,
edges.first().world.name,
xList.toDoubleArray(),
yList.toDoubleArray(),
zList.toDoubleArray(),
true
)
} }
if (style != null) { if (style != null) {
if (style.lineStyle != null) { if (style.lineStyle != null) {

@ -2,9 +2,4 @@ package net.trivernis.chunkmaster.lib.dynmap
import org.dynmap.markers.MarkerIcon import org.dynmap.markers.MarkerIcon
data class MarkerStyle( data class MarkerStyle(val icon: MarkerIcon?, val lineStyle: LineStyle?, val fillStyle: FillStyle?, val boostFlag: Boolean = false)
val icon: MarkerIcon?,
val lineStyle: LineStyle?,
val fillStyle: FillStyle?,
val boostFlag: Boolean = false
)

@ -5,10 +5,6 @@ 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, ((z * 16) + 8).toDouble()) return Location(world, ((x*16) + 8).toDouble(), 1.0, ((z*16) + 8).toDouble())
}
override fun toString(): String {
return "($x, $z)"
} }
} }

@ -1,57 +0,0 @@
package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster
import org.bukkit.Chunk
import java.util.*
import java.util.concurrent.locks.ReentrantReadWriteLock
class ChunkUnloader(private val plugin: Chunkmaster) : Runnable {
private val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks")
private val lock = ReentrantReadWriteLock()
private var unloadingQueue = Vector<Chunk>(maxLoadedChunks)
val isFull: Boolean
get() {
return pendingSize == maxLoadedChunks
}
val pendingSize: Int
get() {
lock.readLock().lock()
val size = unloadingQueue.size
lock.readLock().unlock()
return size
}
/**
* Unloads all chunks in the unloading queue with each run
*/
override fun run() {
lock.writeLock().lock()
try {
val chunkToUnload = unloadingQueue.toHashSet()
for (chunk in chunkToUnload) {
try {
chunk.unload(true)
} catch (e: Exception) {
plugin.logger.severe(e.toString())
}
}
unloadingQueue.clear()
} finally {
lock.writeLock().unlock()
}
}
/**
* Adds a chunk to unload to the queue
*/
fun add(chunk: Chunk) {
lock.writeLock().lockInterruptibly()
try {
unloadingQueue.add(chunk)
} finally {
lock.writeLock().unlock()
}
}
}

@ -1,145 +0,0 @@
package net.trivernis.chunkmaster.lib.generation
import io.papermc.lib.PaperLib
import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.shapes.Shape
import org.bukkit.World
import java.util.concurrent.ArrayBlockingQueue
class DefaultGenerationTask(
private val plugin: Chunkmaster,
unloader: ChunkUnloader,
world: World,
startChunk: ChunkCoordinates,
override val radius: Int = -1,
shape: Shape,
missingChunks: HashSet<ChunkCoordinates>,
state: TaskState
) : GenerationTask(plugin, world, unloader, startChunk, shape, missingChunks, state) {
private val maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks")
val pendingChunks = ArrayBlockingQueue<PendingChunkEntry>(maxPendingChunks)
override var count = 0
override var endReached: Boolean = false
init {
updateGenerationAreaMarker()
count = shape.count
}
/**
* Runs the generation task. Every Iteration the next chunks will be generated if
* they haven't been generated already
* After a configured number of chunks chunks have been generated, they will all be unloaded and saved.
*/
override fun generate() {
generateMissing()
seekGenerated()
generateUntilBorder()
}
/**
* Validates that all chunks have been generated or generates missing ones
*/
override fun validate() {
this.shape.reset()
val missedChunks = HashSet<ChunkCoordinates>()
while (!cancelRun && !borderReached()) {
val chunkCoordinates = nextChunkCoordinates
triggerDynmapRender(chunkCoordinates)
if (!PaperLib.isChunkGenerated(world, chunkCoordinates.x, chunkCoordinates.z)) {
missedChunks.add(chunkCoordinates)
}
}
this.missingChunks.addAll(missedChunks)
}
/**
* Generates chunks that are missing
*/
override fun generateMissing() {
val missing = this.missingChunks.toHashSet()
this.count = 0
while (missing.size > 0 && !cancelRun) {
if (plugin.mspt < msptThreshold && !unloader.isFull) {
val chunk = missing.first()
missing.remove(chunk)
this.requestGeneration(chunk)
this.count++
} else {
Thread.sleep(50L)
}
}
if (!cancelRun) {
this.joinPending()
}
}
/**
* Seeks until it encounters a chunk that hasn't been generated yet
*/
private fun seekGenerated() {
do {
lastChunkCoords = nextChunkCoordinates
count = shape.count
} while (PaperLib.isChunkGenerated(world, lastChunkCoords.x, lastChunkCoords.z) && !borderReached())
}
/**
* Generates the world until it encounters the worlds border
*/
private fun generateUntilBorder() {
var chunkCoordinates: ChunkCoordinates
while (!cancelRun && !borderReached()) {
if (plugin.mspt < msptThreshold && !unloader.isFull) {
chunkCoordinates = nextChunkCoordinates
requestGeneration(chunkCoordinates)
lastChunkCoords = chunkCoordinates
count = shape.count
} else {
Thread.sleep(50L)
}
}
if (!cancelRun) {
joinPending()
}
}
private fun joinPending() {
while (!this.pendingChunks.isEmpty()) {
Thread.sleep(msptThreshold)
}
}
/**
* Request the generation of a chunk
*/
private fun requestGeneration(chunkCoordinates: ChunkCoordinates) {
if (!PaperLib.isChunkGenerated(world, chunkCoordinates.x, chunkCoordinates.z) || PaperLib.isSpigot()) {
val pendingChunkEntry = PendingChunkEntry(
chunkCoordinates,
PaperLib.getChunkAtAsync(world, chunkCoordinates.x, chunkCoordinates.z, true)
)
this.pendingChunks.put(pendingChunkEntry)
pendingChunkEntry.chunk.thenAccept {
this.unloader.add(it)
this.pendingChunks.remove(pendingChunkEntry)
}
}
}
/**
* Cancels the generation task.
* This unloads all chunks that were generated but not unloaded yet.
*/
override fun cancel() {
this.cancelRun = true
this.pendingChunks.forEach { it.chunk.cancel(false) }
updateGenerationAreaMarker(true)
}
}

@ -1,49 +1,25 @@
package net.trivernis.chunkmaster.lib.generation package net.trivernis.chunkmaster.lib.generation
import io.papermc.lib.PaperLib
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
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.Circle
import net.trivernis.chunkmaster.lib.shapes.Square import net.trivernis.chunkmaster.lib.shapes.Spiral
import org.bukkit.Server import org.bukkit.Server
import org.bukkit.World import org.bukkit.World
import java.util.concurrent.CompletableFuture
class GenerationManager(private val chunkmaster: Chunkmaster, private val server: Server) { class GenerationManager(private val chunkmaster: Chunkmaster, private val server: Server) {
val tasks: HashSet<RunningTaskEntry> = HashSet() val tasks: HashSet<RunningTaskEntry> = HashSet()
val pausedTasks: HashSet<PausedTaskEntry> = HashSet() val pausedTasks: HashSet<PausedTaskEntry> = HashSet()
val worldProperties = chunkmaster.sqliteManager.worldProperties val worldCenters: HashMap<String, Pair<Int, Int>> = HashMap()
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")
}
private val pauseOnPlayerCount: Int
get() {
return chunkmaster.config.getInt("generation.pause-on-player-count")
}
private val autostart: Boolean
get() {
return chunkmaster.config.getBoolean("generation.autostart")
}
val loadedChunkCount: Int
get() {
return unloader.pendingSize
}
private val unloader = ChunkUnloader(chunkmaster)
val allTasks: HashSet<TaskEntry> val allTasks: HashSet<TaskEntry>
get() { get() {
if (this.tasks.isEmpty() && this.pausedTasks.isEmpty()) { if (this.tasks.isEmpty() && this.pausedTasks.isEmpty()) {
if (this.worldCenters.isEmpty()) {
this.loadWorldCenters()
}
this.startAll() this.startAll()
if (server.onlinePlayers.size >= pauseOnPlayerCount) { if (server.onlinePlayers.size >= chunkmaster.config.getInt("generation.pause-on-player-count")) {
this.pauseAll() this.pauseAll()
} }
} }
@ -60,17 +36,41 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
*/ */
fun addTask(world: World, radius: Int = -1, shape: String = "square"): Int { fun addTask(world: World, radius: Int = -1, shape: String = "square"): Int {
val foundTask = allTasks.find { it.generationTask.world == world } val foundTask = allTasks.find { it.generationTask.world == world }
if (foundTask == null) { if (foundTask == null) {
val center = worldProperties.getWorldCenter(world.name).join() val centerChunk = if (worldCenters[world.name] == null) {
val centerChunk = if (center == null) {
ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z)
} else { } else {
val center = worldCenters[world.name]!!
ChunkCoordinates(center.first, center.second) ChunkCoordinates(center.first, center.second)
} }
val generationTask = createGenerationTask(world, centerChunk, centerChunk, radius, shape, null) val generationTask = createGenerationTask(world, centerChunk, centerChunk, radius, shape)
val id = generationTasks.addGenerationTask(world.name, centerChunk, radius, shape).join()
chunkmaster.sqliteManager.executeStatement(
"""
INSERT INTO generation_tasks (center_x, center_z, last_x, last_z, world, radius, shape)
values (?, ?, ?, ?, ?, ?, ?)
""",
HashMap(
mapOf(
1 to centerChunk.x,
2 to centerChunk.z,
3 to centerChunk.x,
4 to centerChunk.z,
5 to world.name,
6 to radius,
7 to shape
)
),
null
)
var id = 0
chunkmaster.sqliteManager.executeStatement("""
SELECT id FROM generation_tasks ORDER BY id DESC LIMIT 1
""".trimIndent(), HashMap()) {
it.next()
id = it.getInt("id")
}
generationTask.onEndReached { generationTask.onEndReached {
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_FINISHED", id, it.count)) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_FINISHED", id, it.count))
@ -78,19 +78,13 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
} }
if (!paused) { if (!paused) {
val taskEntry = RunningTaskEntry( val task = server.scheduler.runTaskTimer(
id, chunkmaster, generationTask, 200, // 10 sec delay
generationTask chunkmaster.config.getLong("generation.period")
) )
taskEntry.start() tasks.add(RunningTaskEntry(id, task, generationTask))
tasks.add(taskEntry)
} else { } else {
pausedTasks.add( pausedTasks.add(PausedTaskEntry(id, generationTask))
PausedTaskEntry(
id,
generationTask
)
)
} }
return id return id
@ -108,18 +102,17 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
last: ChunkCoordinates, last: ChunkCoordinates,
id: Int, id: Int,
radius: Int = -1, radius: Int = -1,
shape: String = "square", delay: Long = 200L,
pendingChunks: List<ChunkCoordinates>? shape: String = "square"
) { ) {
if (!paused) { if (!paused) {
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_FOR_WORLD", world.name)) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("RESUME_FOR_WORLD", world.name))
val generationTask = createGenerationTask(world, center, last, radius, shape, pendingChunks) val generationTask = createGenerationTask(world, center, last, radius, shape)
val taskEntry = RunningTaskEntry( val task = server.scheduler.runTaskTimer(
id, chunkmaster, generationTask, delay,
generationTask chunkmaster.config.getLong("generation.period")
) )
taskEntry.start() tasks.add(RunningTaskEntry(id, task, generationTask))
tasks.add(taskEntry)
generationTask.onEndReached { generationTask.onEndReached {
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_FINISHED", id, generationTask.count)) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_FINISHED", id, generationTask.count))
removeTask(id) removeTask(id)
@ -136,31 +129,23 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
} else { } else {
this.tasks.find { it.id == id } this.tasks.find { it.id == id }
} }
try {
if (taskEntry != null) { if (taskEntry != null) {
if (taskEntry.generationTask.isRunning && taskEntry is RunningTaskEntry) { taskEntry.cancel()
taskEntry.cancel(chunkmaster.config.getLong("mspt-pause-threshold")) chunkmaster.sqliteManager.executeStatement("""
} DELETE FROM generation_tasks WHERE id = ?;
generationTasks.deleteGenerationTask(id) """.trimIndent(), HashMap(mapOf(1 to taskEntry.id)),
completedGenerationTasks.addCompletedTask( null
id,
taskEntry.generationTask.world.name,
taskEntry.generationTask.shape.currentRadius(),
taskEntry.generationTask.startChunk,
taskEntry.generationTask.shape.javaClass.simpleName
) )
pendingChunksTable.clearPendingChunks(id)
if (taskEntry is RunningTaskEntry) { if (taskEntry is RunningTaskEntry) {
if (taskEntry.task.isCancelled) {
tasks.remove(taskEntry) tasks.remove(taskEntry)
}
} else if (taskEntry is PausedTaskEntry) { } else if (taskEntry is PausedTaskEntry) {
pausedTasks.remove(taskEntry) pausedTasks.remove(taskEntry)
} }
return true return true
} }
} catch (e: Exception) {
chunkmaster.logger.severe(e.toString())
}
return false return false
} }
@ -174,15 +159,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
saveProgress() // save progress every 30 seconds saveProgress() // save progress every 30 seconds
}, 600, 600) }, 600, 600)
server.scheduler.runTaskLater(chunkmaster, Runnable { server.scheduler.runTaskLater(chunkmaster, Runnable {
this.loadWorldCenters()
this.startAll() this.startAll()
if (server.onlinePlayers.count() >= pauseOnPlayerCount || !autostart) { if (!server.onlinePlayers.isEmpty()) {
if (!autostart) {
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("NO_AUTOSTART"))
}
this.pauseAll() this.pauseAll()
} }
}, 20) }, 20)
server.scheduler.runTaskTimer(chunkmaster, unloader, unloadingPeriod, unloadingPeriod)
} }
/** /**
@ -191,36 +173,40 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
fun stopAll() { fun stopAll() {
val removalSet = HashSet<RunningTaskEntry>() val removalSet = HashSet<RunningTaskEntry>()
for (task in tasks) { for (task in tasks) {
val lastChunk = task.generationTask.lastChunkCoords
val id = task.id val id = task.id
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("SAVING_TASK_PROGRESS", task.id)) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("SAVING_TASK_PROGRESS", task.id))
saveProgressToDatabase(task.generationTask, id).join() saveProgressToDatabase(lastChunk, id)
if (!task.cancel(chunkmaster.config.getLong("mspt-pause-threshold"))) { task.task.cancel()
chunkmaster.logger.warning(chunkmaster.langManager.getLocalized("CANCEL_FAIL", task.id)) task.generationTask.cancel()
} if (task.task.isCancelled) {
removalSet.add(task) removalSet.add(task)
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_CANCELLED", task.id))
} }
tasks.removeAll(removalSet) chunkmaster.logger.info(chunkmaster.langManager.getLocalized("TASK_CANCELED", task.id))
if (unloader.pendingSize > 0) {
chunkmaster.logger.info(chunkmaster.langManager.getLocalized("SAVING_CHUNKS", unloader.pendingSize))
unloader.run()
} }
tasks.removeAll(removalSet)
} }
/** /**
* Starts all generation tasks. * Starts all generation tasks.
*/ */
fun startAll() { fun startAll() {
generationTasks.getGenerationTasks().thenAccept { tasks -> chunkmaster.sqliteManager.executeStatement("SELECT * FROM generation_tasks", HashMap()) { res ->
for (task in tasks) { var count = 0
val world = server.getWorld(task.world) while (res.next()) {
if (world != null) { count++
pendingChunksTable.getPendingChunks(task.id).thenAccept { try {
resumeTask(world, task.center, task.last, task.id, task.radius, task.shape, it) val id = res.getInt("id")
val world = server.getWorld(res.getString("world"))
val center = ChunkCoordinates(res.getInt("center_x"), res.getInt("center_z"))
val last = ChunkCoordinates(res.getInt("last_x"), res.getInt("last_z"))
val radius = res.getInt("radius")
val shape = res.getString("shape")
if (this.tasks.find { it.id == id } == null) {
resumeTask(world!!, center, last, id, radius, 200L + count, shape)
} }
} else { } catch (error: NullPointerException) {
chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TASK_LOAD_FAILED", task.id)) chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("TASK_LOAD_FAILED", res.getInt("id")))
} }
} }
} }
@ -236,12 +222,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
fun pauseAll() { fun pauseAll() {
paused = true paused = true
for (task in tasks) { for (task in tasks) {
pausedTasks.add( pausedTasks.add(PausedTaskEntry(task.id, task.generationTask))
PausedTaskEntry(
task.id,
task.generationTask
)
)
} }
stopAll() stopAll()
} }
@ -256,100 +237,98 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
} }
/** /**
* Saves the task progress * Overload that doesn't need an argument
*/ */
private fun saveProgress() { private fun loadWorldCenters() {
for (task in tasks) { loadWorldCenters(null)
try {
if (task.generationTask.state == TaskState.CORRECTING) {
reportCorrectionProgress(task)
} else {
reportGenerationProgress(task)
} }
saveProgressToDatabase(task.generationTask, task.id)
} catch (error: Exception) { /**
chunkmaster.logger.warning(chunkmaster.langManager.getLocalized("TASK_SAVE_FAILED", error.toString())) * Loads the world centers from the database
error.printStackTrace() */
fun loadWorldCenters(cb: (() -> Unit)?) {
chunkmaster.sqliteManager.executeStatement("SELECT * FROM world_properties", HashMap()) {
while (it.next()) {
worldCenters[it.getString("name")] = Pair(it.getInt("center_x"), it.getInt("center_z"))
} }
cb?.invoke()
} }
} }
/** /**
* Reports the progress for correcting tasks * Updates the center of a world
*/ */
private fun reportCorrectionProgress(task: RunningTaskEntry) { fun updateWorldCenter(worldName: String, center: Pair<Int, Int>) {
val genTask = task.generationTask chunkmaster.sqliteManager.executeStatement("SELECT * FROM world_properties WHERE name = ?", HashMap(mapOf(1 to worldName))) {
val progress = if (genTask.missingChunks.size > 0) { if (it.next()) {
"(${(genTask.count / genTask.missingChunks.size) * 100}%)" chunkmaster.sqliteManager.executeStatement("UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?", HashMap(
} else { mapOf(
"" 1 to center.first,
} 2 to center.second,
chunkmaster.logger.info( 3 to worldName
chunkmaster.langManager.getLocalized(
"TASK_PERIODIC_REPORT_CORRECTING",
task.id,
genTask.world.name,
genTask.count,
progress
) )
), null)
} else {
chunkmaster.sqliteManager.executeStatement("INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)", HashMap(
mapOf(
1 to worldName,
2 to center.first,
3 to center.second
) )
), null)
}
}
worldCenters[worldName] = center
} }
/** /**
* Reports the progress of the chunk generation * Saves the task progress
*/ */
private fun reportGenerationProgress(task: RunningTaskEntry) { private fun saveProgress() {
for (task in tasks) {
try {
val genTask = task.generationTask val genTask = task.generationTask
val (speed, chunkSpeed) = task.generationSpeed val (speed, chunkSpeed) = task.generationSpeed
val progress = val percentage = if (genTask.radius > 0) "(${"%.2f".format(genTask.shape.progress() * 100)}%)" else ""
genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null) val eta = if (genTask.radius > 0 && speed!! > 0) {
val percentage = val etaSeconds = (genTask.shape.progress())/speed
"(${"%.2f".format(progress * 100)}%)" val hours: Int = (etaSeconds/3600).toInt()
val eta = if (speed!! > 0) {
val remaining = 1 - progress
val etaSeconds = remaining / speed
val hours: Int = (etaSeconds / 3600).toInt()
val minutes: Int = ((etaSeconds % 3600) / 60).toInt() val minutes: Int = ((etaSeconds % 3600) / 60).toInt()
val seconds: Int = (etaSeconds % 60).toInt() val seconds: Int = (etaSeconds % 60).toInt()
", ETA: %dh %dmin %ds".format(hours, minutes, seconds) ", ETA: %d:%02d:%02d".format(hours, minutes, seconds)
} else { } else {
"" ""
} }
chunkmaster.logger.info( chunkmaster.logger.info(chunkmaster.langManager.getLocalized(
chunkmaster.langManager.getLocalized(
"TASK_PERIODIC_REPORT", "TASK_PERIODIC_REPORT",
task.id, task.id,
genTask.world.name, genTask.world.name,
genTask.state.toString(),
genTask.count, genTask.count,
percentage, percentage,
eta, eta,
chunkSpeed!!, chunkSpeed!!,
genTask.lastChunkCoords.x, genTask.lastChunkCoords.x,
genTask.lastChunkCoords.z genTask.lastChunkCoords.z))
) saveProgressToDatabase(genTask.lastChunkCoords, task.id)
) genTask.updateLastChunkMarker()
} catch (error: Exception) {
chunkmaster.logger.warning(chunkmaster.langManager.getLocalized("TASK_SAVE_FAILED", error.toString()))
}
}
} }
/** /**
* Saves the generation progress to the database * Saves the generation progress to the database
*/ */
private fun saveProgressToDatabase(generationTask: GenerationTask, id: Int): CompletableFuture<Void> { private fun saveProgressToDatabase(lastChunk: ChunkCoordinates, id: Int) {
val completableFuture = CompletableFuture<Void>() chunkmaster.sqliteManager.executeStatement(
generationTasks.updateGenerationTask(id, generationTask.lastChunkCoords, generationTask.state).thenAccept { """
pendingChunksTable.clearPendingChunks(id).thenAccept { UPDATE generation_tasks SET last_x = ?, last_z = ?
if (generationTask is DefaultGenerationTask) { WHERE id = ?
if (generationTask.pendingChunks.size > 0) { """.trimIndent(),
pendingChunksTable.addPendingChunks(id, generationTask.pendingChunks.map { it.coordinates }) HashMap(mapOf(1 to lastChunk.x, 2 to lastChunk.z, 3 to id)),
} null
} )
pendingChunksTable.addPendingChunks(id, generationTask.missingChunks.toList()).thenAccept {
completableFuture.complete(null)
}
}
}
return completableFuture
} }
/** /**
@ -361,23 +340,18 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
center: ChunkCoordinates, center: ChunkCoordinates,
start: ChunkCoordinates, start: ChunkCoordinates,
radius: Int, radius: Int,
shapeName: String, shapeName: String
pendingChunks: List<ChunkCoordinates>?
): GenerationTask { ): GenerationTask {
val shape = when (shapeName) { val shape = when (shapeName) {
"circle" -> Circle(Pair(center.x, center.z), Pair(start.x, start.z), radius) "circle" -> Circle(Pair(center.x, center.z), Pair(start.x, start.z), radius)
"square" -> Square(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 -> Square(Pair(center.x, center.z), Pair(start.x, start.z), radius) else -> Spiral(Pair(center.x, center.z), Pair(start.x, start.z), radius)
} }
return DefaultGenerationTask( return if (PaperLib.isPaper()) {
chunkmaster, GenerationTaskPaper(chunkmaster, world, start, radius, shape)
unloader, } else {
world, GenerationTaskSpigot(chunkmaster, world, start, radius, shape)
start, }
radius,
shape, pendingChunks?.toHashSet() ?: HashSet(),
TaskState.GENERATING
)
} }
} }

@ -3,32 +3,37 @@ package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.dynmap.* import net.trivernis.chunkmaster.lib.dynmap.*
import net.trivernis.chunkmaster.lib.shapes.Shape import net.trivernis.chunkmaster.lib.shapes.Shape
import org.bukkit.Chunk
import org.bukkit.World import org.bukkit.World
import kotlin.math.ceil import java.lang.Exception
/** /**
* Interface for generation tasks. * Interface for generation tasks.
*/ */
abstract class GenerationTask( abstract class GenerationTask(
plugin: Chunkmaster, private val plugin: Chunkmaster,
val world: World, startChunk: ChunkCoordinates,
protected val unloader: ChunkUnloader, val shape: Shape
val startChunk: ChunkCoordinates,
val shape: Shape,
val missingChunks: HashSet<ChunkCoordinates>,
var state: TaskState
) : ) :
Runnable { Runnable {
abstract val radius: Int abstract val radius: Int
abstract val world: World
abstract var count: Int abstract var count: Int
abstract var endReached: Boolean abstract var endReached: Boolean
var isRunning: Boolean = false
val loadedChunksCount: Int
get() {
return loadedChunks.size
}
protected val loadedChunks: HashSet<Chunk> = HashSet()
var lastChunkCoords = ChunkCoordinates(startChunk.x, startChunk.z) var lastChunkCoords = ChunkCoordinates(startChunk.x, startChunk.z)
protected set protected set
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 var cancelRun: Boolean = false protected val maxLoadedChunks = plugin.config.getInt("generation.max-loaded-chunks")
protected val chunksPerStep = plugin.config.getInt("generation.chunks-per-step")
private var endReachedCallback: ((GenerationTask) -> Unit)? = null private var endReachedCallback: ((GenerationTask) -> Unit)? = null
@ -40,51 +45,16 @@ abstract class GenerationTask(
null null
} }
private val markerAreaStyle = MarkerStyle(null, LineStyle(2, 1.0, 0x0022FF), FillStyle(.0, 0)) private val markerAreaStyle = MarkerStyle(null, LineStyle(2, 1.0, 0x0022FF), FillStyle(.0, 0))
private val markerAreaId = "chunkmaster_genarea_${world.name}" private val markerAreaId = "chunkmaster_genarea"
private val markerAreaName = "Chunkmaster Generation Area (${ceil(shape.total()).toInt()} chunks)" private val markerAreaName = "Chunkmaster Generation Area"
private val markerLastStyle = MarkerStyle(null, LineStyle(2, 1.0, 0x0077FF), FillStyle(.5, 0x0077FF))
private val markerLastId = "chunkmaster_lastchunk"
private val markerLastName = "Chunkmaster Last Chunk"
private val ignoreWorldborder = plugin.config.getBoolean("generation.ignore-worldborder") private val ignoreWorldborder = plugin.config.getBoolean("generation.ignore-worldborder")
abstract fun generate() abstract override fun run()
abstract fun validate()
abstract fun generateMissing()
abstract fun cancel() abstract fun cancel()
override fun run() {
isRunning = true
try {
when (state) {
TaskState.GENERATING -> {
this.generate()
if (!cancelRun) {
this.state = TaskState.VALIDATING
this.validate()
}
if (!cancelRun) {
this.state = TaskState.CORRECTING
this.generateMissing()
}
}
TaskState.VALIDATING -> {
this.validate()
if (!cancelRun) {
this.state = TaskState.CORRECTING
this.generateMissing()
}
}
TaskState.CORRECTING -> {
this.generateMissing()
}
else -> {
}
}
if (!cancelRun && this.borderReached()) {
this.setEndReached()
}
} catch (e: InterruptedException) {
}
isRunning = false
}
val nextChunkCoordinates: ChunkCoordinates val nextChunkCoordinates: ChunkCoordinates
get() { get() {
val nextChunkCoords = shape.next() val nextChunkCoords = shape.next()
@ -99,6 +69,26 @@ abstract class GenerationTask(
|| shape.endReached() || shape.endReached()
} }
/**
* Unloads all chunks that have been loaded
*/
protected fun unloadLoadedChunks() {
for (chunk in loadedChunks) {
if (chunk.isLoaded) {
try {
chunk.unload(true)
} catch (e: Exception) {
plugin.logger.severe(e.toString())
}
}
if (dynmapIntegration) {
dynmap?.triggerRenderOfVolume(chunk.getBlock(0, 0, 0).location, chunk.getBlock(15, 255, 15).location)
}
}
loadedChunks.clear()
}
/** /**
* Updates the dynmap marker for the generation radius * Updates the dynmap marker for the generation radius
*/ */
@ -109,18 +99,25 @@ abstract class GenerationTask(
markerSet?.creUpdatePolyLineMarker( markerSet?.creUpdatePolyLineMarker(
markerAreaId, markerAreaId,
markerAreaName, markerAreaName,
this.shape.getShapeEdgeLocations() this.shape.getShapeEdgeLocations().map { ChunkCoordinates(it.first, it.second).getCenterLocation(this.world) },
.map { ChunkCoordinates(it.first, it.second).getCenterLocation(this.world) },
markerAreaStyle markerAreaStyle
) )
} }
} }
protected fun triggerDynmapRender(chunkCoordinates: ChunkCoordinates) { /**
if (dynmapIntegration) { * Updates the dynmap marker for the generation radius
dynmap?.triggerRenderOfVolume( */
world.getBlockAt(chunkCoordinates.x * 16, 0, chunkCoordinates.z * 16).location, fun updateLastChunkMarker(clear: Boolean = false) {
world.getBlockAt((chunkCoordinates.x * 16) + 16, 255, (chunkCoordinates.z * 16) + 16).location if (clear) {
markerSet?.deleteAreaMarker(markerLastId)
} else if (dynmapIntegration) {
markerSet?.creUpdateAreMarker(
markerLastId,
markerLastName,
this.lastChunkCoords.getCenterLocation(world).chunk.getBlock(0, 0, 0).location,
this.lastChunkCoords.getCenterLocation(world).chunk.getBlock(15, 0, 15).location,
markerLastStyle
) )
} }
} }
@ -131,8 +128,21 @@ abstract class GenerationTask(
private fun setEndReached() { private fun setEndReached() {
endReached = true endReached = true
count = shape.count count = shape.count
updateGenerationAreaMarker(true)
endReachedCallback?.invoke(this) endReachedCallback?.invoke(this)
updateGenerationAreaMarker(true)
updateLastChunkMarker(true)
}
/**
* Performs a check if the border has been reached
*/
protected fun borderReachedCheck(): Boolean {
val done = borderReached()
if (done) {
unloadLoadedChunks()
setEndReached()
}
return done
} }
/** /**

@ -0,0 +1,121 @@
package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.shapes.Shape
import org.bukkit.Chunk
import org.bukkit.World
import java.lang.Exception
import java.util.concurrent.CompletableFuture
class GenerationTaskPaper(
private val plugin: Chunkmaster,
override val world: World,
startChunk: ChunkCoordinates,
override val radius: Int = -1,
shape: Shape
) : GenerationTask(plugin, startChunk, shape) {
private val maxPendingChunks = plugin.config.getInt("generation.max-pending-chunks")
private val pendingChunks = HashSet<CompletableFuture<Chunk>>()
override var count = 0
override var endReached: Boolean = false
init {
updateGenerationAreaMarker()
count = shape.count
}
/**
* Runs the generation task. Every Iteration the next chunks will be generated if
* they haven't been generated already
* After a configured number of chunks chunks have been generated, they will all be unloaded and saved.
*/
override fun run() {
if (plugin.mspt < msptThreshold) {
if (loadedChunks.size > maxLoadedChunks) {
unloadLoadedChunks()
} else if (pendingChunks.size < maxPendingChunks) {
if (borderReachedCheck()) return
var chunk = nextChunkCoordinates
for (i in 0 until chunkSkips) {
if (world.isChunkGenerated(chunk.x, chunk.z)) {
chunk = nextChunkCoordinates
} else {
break
}
}
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
for (i in 0 until chunksPerStep) {
if (borderReached()) break
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
pendingChunks.add(world.getChunkAtAsync(chunk.x, chunk.z, true))
}
chunk = nextChunkCoordinates
}
if (!world.isChunkGenerated(chunk.x, chunk.z)) {
pendingChunks.add(world.getChunkAtAsync(chunk.x, chunk.z, true))
}
}
lastChunkCoords = chunk
count = shape.count
}
}
checkChunksLoaded()
}
/**
* Cancels the generation task.
* This unloads all chunks that were generated but not unloaded yet.
*/
override fun cancel() {
updateGenerationAreaMarker(true)
updateLastChunkMarker(true)
unloadAllChunks()
}
/**
* Cancels all pending chunks and unloads all loaded chunks.
*/
private fun unloadAllChunks() {
for (pendingChunk in pendingChunks) {
if (pendingChunk.isDone) {
loadedChunks.add(pendingChunk.get())
} else {
pendingChunk.cancel(true)
}
}
pendingChunks.clear()
if (loadedChunks.isNotEmpty()) {
lastChunkCoords = ChunkCoordinates(loadedChunks.last().x, loadedChunks.last().z)
}
for (chunk in loadedChunks) {
if (chunk.isLoaded) {
try {
chunk.unload(true);
} catch (e: Exception){
plugin.logger.severe(e.toString())
}
}
}
}
/**
* Checks if some chunks have been loaded and adds them to the loaded chunk set.
*/
private fun checkChunksLoaded() {
val completedEntries = HashSet<CompletableFuture<Chunk>>()
for (pendingChunk in pendingChunks) {
if (pendingChunk.isDone) {
completedEntries.add(pendingChunk)
loadedChunks.add(pendingChunk.get())
} else if (pendingChunk.isCompletedExceptionally || pendingChunk.isCancelled) {
completedEntries.add(pendingChunk)
}
}
pendingChunks.removeAll(completedEntries)
}
}

@ -0,0 +1,72 @@
package net.trivernis.chunkmaster.lib.generation
import net.trivernis.chunkmaster.Chunkmaster
import net.trivernis.chunkmaster.lib.shapes.Shape
import org.bukkit.World
import java.lang.Exception
class GenerationTaskSpigot(
private val plugin: Chunkmaster,
override val world: World,
startChunk: ChunkCoordinates,
override val radius: Int = -1,
shape: Shape
) : GenerationTask(plugin, startChunk, shape) {
override var count = 0
override var endReached: Boolean = false
init {
updateGenerationAreaMarker()
count = shape.count
}
/**
* Runs the generation task. Every Iteration the next chunks will be generated if
* they haven't been generated already
* After a configured number of chunks chunks have been generated, they will all be unloaded and saved.
*/
override fun run() {
if (plugin.mspt < msptThreshold) {
if (loadedChunks.size > maxLoadedChunks) {
unloadLoadedChunks()
} else {
if (borderReachedCheck()) return
var chunk = nextChunkCoordinates
for (i in 0 until chunksPerStep) {
if (borderReached()) break
val chunkInstance = world.getChunkAt(chunk.x, chunk.z)
chunkInstance.load(true)
loadedChunks.add(chunkInstance)
chunk = nextChunkCoordinates
}
val chunkInstance = world.getChunkAt(chunk.x, chunk.z)
chunkInstance.load(true)
loadedChunks.add(chunkInstance)
lastChunkCoords = chunk
count = shape.count
}
}
}
/**
* Cancels the generation task.
* This unloads all chunks that were generated but not unloaded yet.
*/
override fun cancel() {
for (chunk in loadedChunks) {
if (chunk.isLoaded) {
try {
chunk.unload(true)
} catch (e: Exception) {
plugin.logger.severe(e.toString())
}
}
}
updateGenerationAreaMarker(true)
updateLastChunkMarker(true)
}
}

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

@ -1,9 +0,0 @@
package net.trivernis.chunkmaster.lib.generation
import org.bukkit.Chunk
import java.util.concurrent.CompletableFuture
class PendingChunkEntry(val coordinates: ChunkCoordinates, val chunk: CompletableFuture<Chunk>) {
val isDone: Boolean
get() = chunk.isDone
}

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

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

@ -1,24 +0,0 @@
package net.trivernis.chunkmaster.lib.generation
enum class TaskState {
GENERATING {
override fun toString(): String {
return "GENERATING"
}
},
VALIDATING {
override fun toString(): String {
return "VALIDATING"
}
},
CORRECTING {
override fun toString(): String {
return "CORRECTING"
}
},
PAUSING {
override fun toString(): String {
return "PAUSING"
}
},
}

@ -1,8 +0,0 @@
package net.trivernis.chunkmaster.lib.generation.taskentry
import net.trivernis.chunkmaster.lib.generation.GenerationTask
class PausedTaskEntry(
override val id: Int,
override val generationTask: GenerationTask
) : TaskEntry

@ -1,71 +0,0 @@
package net.trivernis.chunkmaster.lib.generation.taskentry
import net.trivernis.chunkmaster.lib.generation.GenerationTask
class RunningTaskEntry(
override val id: Int,
override val generationTask: GenerationTask
) : TaskEntry {
private var lastProgress: Pair<Long, Double>? = null
private var lastChunkCount: Pair<Long, Int>? = null
private var thread = Thread(generationTask)
/**
* Returns the generation Speed
*/
val generationSpeed: Pair<Double?, Double?>
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)
if (lastProgress != null) {
val progressDiff = progress - lastProgress!!.second
val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble() / 1000
generationSpeed = progressDiff / timeDiff
}
if (lastChunkCount != null) {
val chunkDiff = generationTask.count - lastChunkCount!!.second
val timeDiff = (System.currentTimeMillis() - lastChunkCount!!.first).toDouble() / 1000
chunkGenerationSpeed = chunkDiff / timeDiff
}
lastProgress = Pair(System.currentTimeMillis(), progress)
lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count)
return Pair(generationSpeed, chunkGenerationSpeed)
}
init {
lastProgress = Pair(System.currentTimeMillis(), generationTask.shape.progress(null))
lastChunkCount = Pair(System.currentTimeMillis(), generationTask.count)
}
fun start() {
thread.start()
}
fun cancel(timeout: Long): Boolean {
if (generationTask.isRunning) {
generationTask.cancel()
thread.interrupt()
}
return try {
joinThread(timeout)
} catch (e: InterruptedException) {
true
}
}
private fun joinThread(timeout: Long): Boolean {
var threadStopped = false
for (i in 0..100) {
if (!thread.isAlive || !generationTask.isRunning) {
threadStopped = true
break
}
Thread.sleep(timeout / 100)
}
return threadStopped
}
}

@ -1,11 +0,0 @@
package net.trivernis.chunkmaster.lib.generation.taskentry
import net.trivernis.chunkmaster.lib.generation.GenerationTask
/**
* Generic task entry
*/
interface TaskEntry {
val id: Int
val generationTask: GenerationTask
}

@ -1,12 +1,16 @@
package net.trivernis.chunkmaster.lib.shapes package net.trivernis.chunkmaster.lib.shapes
import net.trivernis.chunkmaster.lib.dynmap.ExtendedMarkerSet
import net.trivernis.chunkmaster.lib.dynmap.MarkerStyle
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashSet import kotlin.collections.HashSet
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt
import kotlin.system.exitProcess
class Circle(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape(center, start, radius) { class Circle(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int): Shape(center, start, radius) {
private var r = 0 private var r = 0
private var coords = Stack<Pair<Int, Int>>() private var coords = Stack<Pair<Int, Int>>()
private var previousCoords = HashSet<Pair<Int, Int>>() private var previousCoords = HashSet<Pair<Int, Int>>()
@ -16,17 +20,9 @@ class Circle(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
return radius > 0 && coords.isEmpty() && r >= radius return radius > 0 && coords.isEmpty() && r >= radius
} }
override fun total(): Double { override fun progress(): Double {
return (PI * radius.toFloat().pow(2))
}
override fun progress(maxRadius: Int?): Double {
// TODO: Radius inner progress // TODO: Radius inner progress
return if (maxRadius != null) { return (count/(PI* radius.toFloat().pow(2))).coerceAtMost(100.0)
(count / (PI * maxRadius.toFloat().pow(2))).coerceAtMost(1.0)
} else {
(count / (PI * radius.toFloat().pow(2))).coerceAtMost(1.0)
}
} }
override fun currentRadius(): Int { override fun currentRadius(): Int {
@ -40,7 +36,7 @@ class Circle(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
override fun getShapeEdgeLocations(): List<Pair<Int, Int>> { override fun getShapeEdgeLocations(): List<Pair<Int, Int>> {
val locations = this.getCircleCoordinates(this.radius) val locations = this.getCircleCoordinates(this.radius)
locations.add(locations.first()) locations.add(locations.first())
return locations.map { Pair(it.first + center.first, it.second + center.second) } return locations.map{ Pair(it.first + center.first, it.second + center.second) }
} }
/** /**
@ -50,30 +46,26 @@ class Circle(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
if (endReached()) { if (endReached()) {
return currentPos return currentPos
} }
if (count == 0 && currentPos != center) { if (count == 0 && currentPos != center) {
val tmpCircle = Circle(center, center, radius) val tmpCircle = Circle(center, center, radius)
while (tmpCircle.next() != currentPos && !tmpCircle.endReached()); while (tmpCircle.next() != currentPos);
this.count = tmpCircle.count this.count = tmpCircle.count
this.r = tmpCircle.r this.r = tmpCircle.r
} }
if (count == 0) { if (count == 0) {
count++ count++
return center return center
} }
if (coords.isEmpty()) { if (coords.isEmpty()) {
r++ r++
val tmpCoords = HashSet<Pair<Int, Int>>() val tmpCoords = HashSet<Pair<Int, Int>>()
tmpCoords.addAll(getCircleCoordinates((r * 2) - 1).map { Pair(it.first / 2, it.second / 2) }) tmpCoords.addAll(getCircleCoordinates((r*2)-1).map { Pair(it.first / 2, it.second / 2) })
tmpCoords.addAll(getCircleCoordinates(r)) tmpCoords.addAll(getCircleCoordinates(r))
tmpCoords.removeAll(previousCoords) tmpCoords.removeAll(previousCoords)
previousCoords.clear() previousCoords.clear()
coords.addAll(tmpCoords) coords.addAll(tmpCoords)
previousCoords.addAll(tmpCoords) previousCoords.addAll(tmpCoords)
} }
count++ count++
val coord = coords.pop() val coord = coords.pop()
currentPos = Pair(coord.first + center.first, coord.second + center.second) currentPos = Pair(coord.first + center.first, coord.second + center.second)
@ -85,18 +77,16 @@ class Circle(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
* Some coordinates might already be present in the list * Some coordinates might already be present in the list
* @param r - the radius * @param r - the radius
*/ */
private fun getCircleCoordinates(r: Int): Vector<Pair<Int, Int>> { private fun getCircleCoordinates(r: Int): ArrayList<Pair<Int, Int>> {
val coords = Vector<Pair<Int, Int>>() val coords = ArrayList<Pair<Int, Int>>()
val segCoords = getSegment(r) val segCoords = getSegment(r)
coords.addAll(segCoords.reversed()) coords.addAll(segCoords.reversed())
for (step in 1..7) { for (step in 1..7) {
val tmpSeg = Vector<Pair<Int, Int>>() val tmpSeg = ArrayList<Pair<Int, Int>>()
for (pos in segCoords) { for (pos in segCoords) {
val coord = when (step) { val coord = when (step) {
1 -> Pair(pos.first, -pos.second) 1 -> Pair(pos.first, -pos.second)
2 -> Pair(pos.second, -pos.first) 2 ->Pair(pos.second, -pos.first)
3 -> Pair(-pos.second, -pos.first) 3 -> Pair(-pos.second, -pos.first)
4 -> Pair(-pos.first, -pos.second) 4 -> Pair(-pos.first, -pos.second)
5 -> Pair(-pos.first, pos.second) 5 -> Pair(-pos.first, pos.second)
@ -138,11 +128,4 @@ class Circle(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
} }
return coords return coords
} }
override fun reset() {
this.r = 0
this.currentPos = center
this.previousCoords.clear()
this.count = 0
}
} }

@ -1,5 +1,9 @@
package net.trivernis.chunkmaster.lib.shapes package net.trivernis.chunkmaster.lib.shapes
import net.trivernis.chunkmaster.lib.dynmap.ExtendedMarkerSet
import net.trivernis.chunkmaster.lib.dynmap.MarkerStyle
import javax.xml.stream.Location
abstract class Shape(protected val center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) { abstract class Shape(protected val center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) {
protected var currentPos = start protected var currentPos = start
protected var radius = radius protected var radius = radius
@ -19,12 +23,7 @@ abstract class Shape(protected val center: Pair<Int, Int>, start: Pair<Int, Int>
/** /**
* Returns the progress of the shape * Returns the progress of the shape
*/ */
abstract fun progress(maxRadius: Int?): Double abstract fun progress(): Double
/**
* The total number of chunks to generate
*/
abstract fun total(): Double
/** /**
* Returns the current radius * Returns the current radius
@ -35,9 +34,4 @@ abstract class Shape(protected val center: Pair<Int, Int>, start: Pair<Int, Int>
* returns a poly marker for the shape * returns a poly marker for the shape
*/ */
abstract fun getShapeEdgeLocations(): List<Pair<Int, Int>> abstract fun getShapeEdgeLocations(): List<Pair<Int, Int>>
/**
* Resets the shape to its center start position
*/
abstract fun reset()
} }

@ -1,29 +1,20 @@
package net.trivernis.chunkmaster.lib.shapes package net.trivernis.chunkmaster.lib.shapes
import kotlin.math.PI
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt
class Square(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape(center, start, radius) { class Spiral(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int): Shape(center, start, radius) {
private var direction = 0 private var direction = 0
override fun endReached(): Boolean { override fun endReached(): Boolean {
val distances = getDistances(center, currentPos) val distances = getDistances(center, currentPos)
return radius > 0 && ((direction == 3 return radius > 0 && (distances.first > radius || distances.second > radius)
&& abs(distances.first) == abs(distances.second)
&& abs(distances.first) == radius)
|| (distances.first > radius || distances.second > radius))
} }
override fun total(): Double { override fun progress(): Double {
return (radius * 2).toDouble().pow(2) return (count / (radius * 2).toDouble().pow(2)).coerceAtMost(100.0)
}
override fun progress(maxRadius: Int?): Double {
return if (maxRadius != null) {
(count / (maxRadius * 2).toDouble().pow(2)).coerceAtMost(1.0)
} else {
(count / (radius * 2).toDouble().pow(2)).coerceAtMost(1.0)
}
} }
override fun currentRadius(): Int { override fun currentRadius(): Int {
@ -35,31 +26,28 @@ class Square(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
* Returns the next value in the spiral * Returns the next value in the spiral
*/ */
override fun next(): Pair<Int, Int> { override fun next(): Pair<Int, Int> {
if (endReached()) {
return currentPos
}
if (count == 0 && currentPos != center) { if (count == 0 && currentPos != center) {
// simulate the spiral to get the correct direction and count // simulate the spiral to get the correct direction and count
val simSpiral = Square(center, center, radius) val simSpiral = Spiral(center, center, radius)
while (simSpiral.next() != currentPos && !simSpiral.endReached()); 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)
} }
@ -77,7 +65,7 @@ class Square(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
direction = 0 direction = 0
} }
} }
count++ count ++
return currentPos return currentPos
} }
@ -98,13 +86,4 @@ class Square(center: Pair<Int, Int>, start: Pair<Int, Int>, radius: Int) : Shape
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)
} }
/**
* Resets the shape to its starting parameters
*/
override fun reset() {
this.currentPos = center
this.count = 0
this.direction = 0
}
} }

@ -1,12 +1,11 @@
RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'... RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'...
TASK_FINISHED = Task #%d finished after %d chunks. TASK_FINISHED = Task #%d finished after %d chunks.
TASK_CANCELLED = Cancelled task #%s. TASK_CANCELED = Canceled task #%s.
TASK_LOAD_FAILED = §cFailed to load task #%d. TASK_LOAD_FAILED = §cFailed to load task #%d.
TASK_LOAD_SUCCESS = %d saved tasks loaded. TASK_LOAD_SUCCESS = %d saved tasks loaded.
TASK_NOT_FOUND = §cTask %s not found! TASK_NOT_FOUND = §cTask %s not found!
CREATE_DELAYED_LOAD = Creating task to load chunk generation Tasks later... CREATE_DELAYED_LOAD = Creating task to load chunk generation Tasks later...
TASK_PERIODIC_REPORT = Task #%d running for '%s'. State: %s. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d TASK_PERIODIC_REPORT = Task #%d running for '%s'. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d
TASK_PERIODIC_REPORT_CORRECTING = Task #%d generating missing chunks for '%s'. Progress: %d chunks %s
TASK_SAVE_FAILED = §cException when saving tasks: %s TASK_SAVE_FAILED = §cException when saving tasks: %s
WORLD_NAME_REQUIRED = §cYou need to provide a world name! WORLD_NAME_REQUIRED = §cYou need to provide a world name!
@ -19,14 +18,10 @@ TASK_ID_REQUIRED = §cYou need to provide a task id!
INVALID_ARGUMENT = §cInvalid argument at %s: %s! INVALID_ARGUMENT = §cInvalid argument at %s: %s!
PAUSED_TASKS_HEADER = Currently Paused Generation Tasks PAUSED_TASKS_HEADER = Currently Paused Generation Tasks
TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%s§r - §2%s chunks %s§r TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%d chunks %s§r
RUNNING_TASKS_HEADER = Currently Running Generation Tasks RUNNING_TASKS_HEADER = Currently Running Generation Tasks
NO_GENERATION_TASKS = There are no 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. PAUSE_SUCCESS = §9Paused all generation tasks.
ALREADY_PAUSED = §cThe generation process is already paused! ALREADY_PAUSED = §cThe generation process is already paused!
@ -78,8 +73,3 @@ STATS_WORLD_NAME = §l%s§r
STATS_ENTITY_COUNT = - §2%d§r Entities STATS_ENTITY_COUNT = - §2%d§r Entities
STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks
STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster 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!
NO_AUTOSTART = Autostart set to §2false§r. Pausing...

@ -1,12 +1,11 @@
RESUME_FOR_WORLD = Setze das Chunk-Generieren für Welt '%s' fort... RESUME_FOR_WORLD = Setze das Chunk-Generieren für Welt '%s' fort...
TASK_FINISHED = Aufgabe #%d wurde nach %d chunks beendet. TASK_FINISHED = Aufgabe #%d wurde nach %d chunks beendet.
TASK_CANCELLED = Aufgabe #%s wurde abgebrochen. TASK_CANCELED = Aufgabe #%s wurde abgebrochen.
TASK_LOAD_FAILED = §cAufgabe #%d konnte nicht geladen werden. TASK_LOAD_FAILED = §cAufgabe #%d konnte nicht geladen werden.
TASK_LOAD_SUCCESS = %d gespeicherte Aufgaben wurden geladen. TASK_LOAD_SUCCESS = %d gespeicherte Aufgaben wurden geladen.
TASK_NOT_FOUND = §cAufgabe %s konnte nicht gefunden werden! TASK_NOT_FOUND = §cAufgabe %s konnte nicht gefunden werden!
CREATE_DELAYED_LOAD = Erstelle einen Bukkit-Task zum verzögerten Laden von Aufgaben... CREATE_DELAYED_LOAD = Erstelle einen Bukkit-Task zum verzögerten Laden von Aufgaben...
TASK_PERIODIC_REPORT = Aufgabe #%d für Welt '%s'. Status: %s. Fortschritt: %d chunks %s %s, Geschwindigkeit: %.1f ch/s, Letzer Chunk: %d, %d TASK_PERIODIC_REPORT = Aufgabe #%d für Welt '%s'. Fortschritt: %d chunks %s %s, Geschwindigkeit: %.1f ch/s, Letzer Chunk: %d, %d
TASK_PERIODIC_REPORT_CORRECTING = Aufgabe #%d generiert fehlende Chunks für Welt '%s'. Fortschritt: %d chunks %s
TASK_SAVE_FAILED = §cFehler beim Speichern der Aufgaben: %s TASK_SAVE_FAILED = §cFehler beim Speichern der Aufgaben: %s
WORLD_NAME_REQUIRED = §cDu musst einen Weltennamen angeben! WORLD_NAME_REQUIRED = §cDu musst einen Weltennamen angeben!
@ -74,13 +73,3 @@ STATS_WORLD_NAME = §l%s§r
STATS_ENTITY_COUNT = - §2%d§r Entities STATS_ENTITY_COUNT = - §2%d§r Entities
STATS_LOADED_CHUNKS = - §2%d§r Geladene Chunks STATS_LOADED_CHUNKS = - §2%d§r Geladene Chunks
STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r von Chunkmaster geladene Chunks 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

@ -1,11 +1,11 @@
RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'... RESUME_FOR_WORLD = Resuming chunk generation task for world '%s'...
TASK_FINISHED = Task #%d finished after %d chunks. TASK_FINISHED = Task #%d finished after %d chunks.
TASK_CANCELLED = Cancelled task #%s. TASK_CANCELED = Canceled task #%s.
TASK_LOAD_FAILED = §cFailed to load task #%d. TASK_LOAD_FAILED = §cFailed to load task #%d.
TASK_LOAD_SUCCESS = %d saved tasks loaded. TASK_LOAD_SUCCESS = %d saved tasks loaded.
TASK_NOT_FOUND = §cTask %s not found! TASK_NOT_FOUND = §cTask %s not found!
CREATE_DELAYED_LOAD = Creating task to load chunk generation Tasks later... CREATE_DELAYED_LOAD = Creating task to load chunk generation Tasks later...
TASK_PERIODIC_REPORT = Task #%d running for '%s'. State: %s. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d TASK_PERIODIC_REPORT = Task #%d running for '%s'. Progress: %d chunks %s %s, Speed: %.1f ch/s, Last Chunk: %d, %d
TASK_SAVE_FAILED = §cException when saving tasks: %s TASK_SAVE_FAILED = §cException when saving tasks: %s
WORLD_NAME_REQUIRED = §cYou need to provide a world name! WORLD_NAME_REQUIRED = §cYou need to provide a world name!
@ -18,6 +18,7 @@ TASK_ID_REQUIRED = §cYou need to provide a task id!
INVALID_ARGUMENT = §cInvalid argument at %s: %s! INVALID_ARGUMENT = §cInvalid argument at %s: %s!
PAUSED_TASKS_HEADER = Currently Paused Generation Tasks PAUSED_TASKS_HEADER = Currently Paused Generation Tasks
TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%d chunks %s§r
RUNNING_TASKS_HEADER = Currently Running Generation Tasks RUNNING_TASKS_HEADER = Currently Running Generation Tasks
NO_GENERATION_TASKS = There are no generation tasks. NO_GENERATION_TASKS = There are no generation tasks.
@ -72,13 +73,3 @@ STATS_WORLD_NAME = §l%s§r
STATS_ENTITY_COUNT = - §2%d§r Entities STATS_ENTITY_COUNT = - §2%d§r Entities
STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks STATS_LOADED_CHUNKS = - §2%d§r Loaded Chunks
STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks Loaded by Chunkmaster 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

@ -1,85 +0,0 @@
RESUME_FOR_WORLD = Resumiendo tarea de generación de chunks para el mundo '%s'...
TASK_FINISHED = Tarea #%d terminada después de %d chunks.
TASK_CANCELLED = Tarea #%s cancelada.
TASK_LOAD_FAILED = §cFallo al cargar la tarea #%d.
TASK_LOAD_SUCCESS = %d tareas guardadas cargadas.
TASK_NOT_FOUND = §c¡Tarea %s no encontrada!
CREATE_DELAYED_LOAD = Creando tarea Bukkit para cargar la generación de chunks...
TASK_PERIODIC_REPORT = Tarea #%d en ejecución para '%s'. Estado: %s. Progreso: %d Chunks %s %s, Velocidad: %.1f ch/s, Último Chunk: %d, %d
TASK_PERIODIC_REPORT_CORRECTING = Tarea #%d gerenando chunks perdidos para '%s'. Progreso: %d Chunks %s
TASK_SAVE_FAILED = §cExcepción al guardar las tareas: %s
WORLD_NAME_REQUIRED = §c¡Tienes que proporcionar un nombre del mundo!
WORLD_NOT_FOUND = §c¡Mundo §2%s §cno encontrado!
TASK_ALREADY_EXISTS = §c¡Una tarea para '%s' ya existe!
TASK_CREATION_SUCCESS = §9¡Tarea de generación para el mundo §2%s§9 §2%s§9 y forma %s creada con éxito!
TASK_UNIT_WORLDBORDER = hasta el borde del mundo
TASK_UNIT_RADIUS = con un radius de %s
TASK_ID_REQUIRED = §c¡Necesitas proporcionar una id de tarea!
INVALID_ARGUMENT = §c¡Argumento invalido en %s: %s!
PAUSED_TASKS_HEADER = Tareas de generación actualmente en pausa
TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%s§r - §2%s Chunks %s§r
RUNNING_TASKS_HEADER = Tareas de generación en curso
NO_GENERATION_TASKS = No hay tareas de generación.
COMPLETED_TASKS_HEADER = §nTareas de generación completadas§r
COMPLETED_WORLD_HEADER = §l%s§r
COMPLETED_TASK_ENTRY = - §9#%d§r: §2%d§r radio de chunks desde el centro §2(%d, %d)§r con forma §2%s§r
PAUSE_SUCCESS = §9Tareas de generación pausadas
ALREADY_PAUSED = §cEl progreso de generación ya está en pausa.
RESUME_SUCCESS = §9Tareas de generación renaudadas
NOT_PAUSED = §cEl progreso de generación no está en pausa.
CONFIG_RELOADING = Recargando el archivo de configuración...
CONFIG_RELOADED = §2El archivo de configuración ha sido recargado!
TELEPORTED = §9Has sido teletransportado al chunk §2%s, %s
TP_ONLY_PLAYER = §c¡Este comando solo puede ser ejecutado por un jugador!
NO_PERMISSION = §c¡No tienes permiso para este comando!
SUBCOMMAND_NOT_FOUND = §c¡Subcomando §2%s §cno encontrado!
STOPPING_ALL_TASKS = Deteniendo todas las tareas de generación...
SAVING_TASK_PROGRESS = Guardando el progreso de la tarea #%s en la base de datos...
DB_INIT = Iniciando la base de datos...
DB_INIT_FINISHED = Base de datos ha sido inicializada.
DB_INIT_EROR = Error al inicializar la base de datos: %s.
DATABASE_CONNECTION_ERROR = §c¡No se ha podido establecer la conexión con la base de datos!
SQL_ERROR = §cSe ha producido un error en sql %s!
NO_DATABASE_CONNECTION = §cNo se ha podido ejecutar sql: No hay conexión con la base de datos.
CREATE_TABLE_DEFINITION = Tabla %s creada con definición %s.
TABLE_CREATE_ERROR = §cSe ha producido un error al crear la tabla %s.
UPDATE_TABLE_DEFINITION = Tabla %s actualizada con sql %s.
UPDATE_TABLE_FAILED = Se ha producido un error al actualizar la tabla %s con sql %s.
TOO_FEW_ARGUMENTS = §cNo has aportado suficientes argumentos.
COORD_INVALID = §cLas coordenadas proporcionadas ('%s', '%s') son incorrectas!
CENTER_UPDATED = §9El centro del mundo §2%s §9se ha actualizado a §2(%s, %s)§9.
CENTER_INFO = §9El centro del mundo §2%s §9es §2(%s, %s)§9.
PLUGIN_DETECTED = ¡Plugin %s con versión %s detectada!
RESUME_PLAYER_LEAVE = El número de jugadores es menor que el configurado como valor de pausa. Reanudando generación...
PAUSE_PLAYER_JOIN = El número de jugadores es igual que el configurado como valor de pausa. Pausando generación...
PAUSE_MANUALLY = Generación se pausó manualmente. No se resumirá automáticamente.
STATS_HEADER = §nEstadísticas§r
STATS_SERVER = §lEstadísticas del Servidor§r
STATS_SERVER_VERSION = - Versión del Servidor: §2%s§r
STATS_PLUGIN_VERSION = - Versión del Plugin: §2%s§r
STATS_MEMORY = - Memoria (u/a): §2%d mb / %d mb = (%.2f porcentaje)§r
STATS_CORES = - Cores: §2%d§r
STATS_WORLD_NAME = §l%s§r
STATS_ENTITY_COUNT = - §2%d§r Entidades
STATS_LOADED_CHUNKS = - §2%d§r Chunks cargados
STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks cargados por Chunkmaster
STATS_GENERATING = - Generando: §2%s§r
SAVING_CHUNKS = Guardando %d chunks guardados...
CANCEL_FAIL = Fallo en la cancelación de la tarea #%d en el tiempo de espera configurado!
NO_AUTOSTART = Autoarranque configurado a §2false§r. Pausando...

@ -1,75 +0,0 @@
RESUME_FOR_WORLD = Reprise de la tâche de génération pour le monde '%s'...
TASK_FINISHED = Tâche #%d terminée après %d chunks.
TASK_CANCELED = Tâche #%s annulée.
TASK_LOAD_FAILED = §cImpossible de charger la tâche #%d.
TASK_LOAD_SUCCESS = %d tâches chargées avec succès.
TASK_NOT_FOUND = §cTâche %s introuvable!
CREATE_DELAYED_LOAD = Création de la tâche pour la génération du terrain plus tard...
TASK_PERIODIC_REPORT = Tâche #%d en cours pour '%s'. État: %s. Progression: %d chunks %s %s, Vitesse: %.1f ch/s, Dernier Chunk: %d, %d
TASK_SAVE_FAILED = §cErreur lors de la sauvegarde de la tâche: %s
WORLD_NAME_REQUIRED = §cVous devez renseigner le nom d'un monde!
WORLD_NOT_FOUND = §cMonde §2%s §cintrouvable!
TASK_ALREADY_EXISTS = §cUne tâche pour '%s' existe déjà!
TASK_CREATION_SUCCESS = §9Tâche de génération pour le monde §2%s§9 §2%s§9 avec une forme %s créée avec succès!
TASK_UNIT_WORLDBORDER = jusqu'à la limite du monde
TASK_UNIT_RADIUS = avec un rayon de %s
TASK_ID_REQUIRED = §cVous devez renseigner l'id de la tache!
INVALID_ARGUMENT = §cArgument invalide à %s: %s!
PAUSED_TASKS_HEADER = Tâches de génération en pause
TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%s§r - §2%s chunks %s§r
RUNNING_TASKS_HEADER = Tâches de génération en cours
NO_GENERATION_TASKS = Il n'y a aucune tâche de génération.
PAUSE_SUCCESS = §9Mise en pause de toutes les tâches de génération.
ALREADY_PAUSED = §cLa génération est déjà en pause!
RESUME_SUCCESS = §9Reprise de toutes les tâches de génération.
NOT_PAUSED = §cLa génération n'est pas en pause!
CONFIG_RELOADING = Rechargement du fichier de configuration...
CONFIG_RELOADED = §2La configuration a été rechargé!
TELEPORTED = §9Vous avez été téléporté au chunk §2%s, %s
TP_ONLY_PLAYER = §cCette commande ne peut être exécutée que par un joueur!
NO_PERMISSION = §cVous n'avez pas la permission d'exécuter cette commande!
SUBCOMMAND_NOT_FOUND = §cSSous-commande §2%s §cintrouvable!
STOPPING_ALL_TASKS = Arrêts de toutes les tâches de génération...
SAVING_TASK_PROGRESS = Sauvegarde de la progression de la tâche #%s dans la base de données...
DB_INIT = Initialisation de la base de données...
DB_INIT_FINISHED = Base de données initialisée.
DB_INIT_EROR = Erreur lors de l'initialisation de la base de données: %s.
DATABASE_CONNECTION_ERROR = §cConnexion impossible à la base de données!
SQL_ERROR = §cErreur lors de l'exécution SQL %s!
NO_DATABASE_CONNECTION = §cImpossible d'exécuter une requête SQL: Pas de connexion à la base de données.
CREATE_TABLE_DEFINITION = Table créée %s avec pour définition %s.
TABLE_CREATE_ERROR = §cErreur lors de la création de la table %s.
UPDATE_TABLE_DEFINITION = Mise à jour de la table %s par la requête SQL %s.
UPDATE_TABLE_FAILED = Impossible de mettre à jour la table %s par la requête SQL %s.
TOO_FEW_ARGUMENTS = §cIl manques des arguments pour exécuter la commande.
COORD_INVALID = §cLes coordonnées renseignées ('%s', '%s') sont invalides!
CENTER_UPDATED = §9Le centre du monde §2%s §9a été défini en §2(%s, %s)§9.
CENTER_INFO = §9Le centre du monde §2%s §9est §2(%s, %s)§9.
PLUGIN_DETECTED = Version de %s détectée %s
RESUME_PLAYER_LEAVE = Le nombre de joueurs est plus bas que la limite configurée. Reprise de la génération...
PAUSE_PLAYER_JOIN = Le nombre de joueurs a atteint la limite configurée. Arrêt de la génération...
PAUSE_MANUALLY = La génération a été manuellement arrêtée. Celle-ci ne reprendra pas automatiquement.
STATS_HEADER = §nStatistiques§r
STATS_SERVER = §lStatistiques serveur§r
STATS_SERVER_VERSION = - Version du serveur: §2%s§r
STATS_PLUGIN_VERSION = - Version du plugin: §2%s§r
STATS_MEMORY = - Mémoire vive (u/a): §2%d mb / %d mb = (%.2f pourcent)§r
STATS_CORES = - Coeurs processeur: §2%d§r
STATS_WORLD_NAME = §l%s§r
STATS_ENTITY_COUNT = - §2%d§r Entités
STATS_LOADED_CHUNKS = - §2%d§r Chunks chargés
STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r Chunks chargés par Chunkmaster

@ -1,85 +0,0 @@
RESUME_FOR_WORLD = 正在恢复执行 '%s' 世界的区块生成任务...
TASK_FINISHED = 任务 #%d 在生成 %d 个区块后完成.
TASK_CANCELLED = 已取消任务 #%s.
TASK_LOAD_FAILED = §c加载任务 #%d 失败.
TASK_LOAD_SUCCESS = %d 个已保存的任务加载完成.
TASK_NOT_FOUND = §c任务 %s 未找到!
CREATE_DELAYED_LOAD = 正在创建延迟执行的区块生成任务...
TASK_PERIODIC_REPORT = 任务 #%d 正在 '%s' 世界执行. 状态: %s. 进度: %d 区块 %s %s, 速度: %.1f 区块 / 秒, 最新生成的区块: %d, %d
TASK_PERIODIC_REPORT_CORRECTING = 任务 #%d 正在为世界 '%s' 生成缺失的区块. 进度: %d 区块 %s
TASK_SAVE_FAILED = §c保存任务时发生错误: %s
WORLD_NAME_REQUIRED = §c你需要提供世界名称!
WORLD_NOT_FOUND = §c无法找到名为 §2%s §c的世界!
TASK_ALREADY_EXISTS = §c已存在 '%s' 的任务!
TASK_CREATION_SUCCESS = §9已创建 §2%s§9 世界的区块生成任务§2%s§9 的 %s 区域!
TASK_UNIT_WORLDBORDER = 直到世界边界
TASK_UNIT_RADIUS = 半径 %s
TASK_ID_REQUIRED = §c你需要提供任务 ID!
INVALID_ARGUMENT = §c在 %s: %s 存在无效的变量!
PAUSED_TASKS_HEADER = 当前暂停的区块生成任务
TASKS_ENTRY = - §9#%d§r - §2%s§r - §2%s§r - §2%s 区块 %s§r
RUNNING_TASKS_HEADER = 当前运行的区块生成任务
NO_GENERATION_TASKS = 无区块生成任务.
COMPLETED_TASKS_HEADER = §n已完成任务列表§r
COMPLETED_WORLD_HEADER = §l%s§r
COMPLETED_TASK_ENTRY = - §9#%d§r: §2%d§r 区块半径, 中心点 §2(%d, %d)§r , 形状 §2%s§r
PAUSE_SUCCESS = §9已暂停所有区块生成任务.
ALREADY_PAUSED = §c区块生成进程已经暂停!
RESUME_SUCCESS = §9已恢复执行所有区块生成任务.
NOT_PAUSED = §c区块生成进程并未暂停!
CONFIG_RELOADING = 正在重载配置文件...
CONFIG_RELOADED = §2配置文件重载完成!
TELEPORTED = §9你已被传送到区块 §2%s, %s
TP_ONLY_PLAYER = §c此命令只能由玩家执行!
NO_PERMISSION = §c你无权执行此命令!
SUBCOMMAND_NOT_FOUND = §c子命令 §2%s §c未找到!
STOPPING_ALL_TASKS = 正在停止所有区块生成任务...
SAVING_TASK_PROGRESS = 正在保存任务进度 #%s 到数据库中...
DB_INIT = 正在初始化数据库...
DB_INIT_FINISHED = 数据库初始化完成.
DB_INIT_EROR = 初始化数据库时发生错误: %s.
DATABASE_CONNECTION_ERROR = §c连接数据库失败!
SQL_ERROR = §cSQL 发生错误: %s !
NO_DATABASE_CONNECTION = §c无法执行 SQL 语句: 无数据库连接.
CREATE_TABLE_DEFINITION = 已创建表 %s ,定义 %s.
TABLE_CREATE_ERROR = §c创建表 %s 失败.
UPDATE_TABLE_DEFINITION = 已更新表 %s SQL 语句为 %s.
UPDATE_TABLE_FAILED = 无法更新表 %s SQL 语句为 %s.
TOO_FEW_ARGUMENTS = §c你没有提供足够的参数.
COORD_INVALID = §c提供的坐标 ('%s', '%s') 无效!
CENTER_UPDATED = §9世界 §2%s §9的中心已设为 §2(%s, %s)§9.
CENTER_INFO = §9世界 §2%s §9的中心为 §2(%s, %s)§9.
PLUGIN_DETECTED = 检测到 %s 版本 %s
RESUME_PLAYER_LEAVE = 在线玩家数量小于配置文件设定的阈值。正在恢复生成...
PAUSE_PLAYER_JOIN = 在线玩家数量达到配置文件设定的阈值。正在暂停生成...
PAUSE_MANUALLY = 生成进程已被手动暂停。将不会自动重启任务.
STATS_HEADER = §n状态与统计§r
STATS_SERVER = §l服务器状态§r
STATS_SERVER_VERSION = - 服务器版本: §2%s§r
STATS_PLUGIN_VERSION = - 插件版本: §2%s§r
STATS_MEMORY = - 内存 (u/a): §2%d MB / %d MB = (%.2f percent)§r
STATS_CORES = - CPU 核心: §2%d§r
STATS_WORLD_NAME = §l%s§r
STATS_ENTITY_COUNT = - §2%d§r 实体
STATS_LOADED_CHUNKS = - §2%d§r 已载入区块
STATS_PLUGIN_LOADED_CHUNKS = - §2%d§r 被 Chunk Master 载入的区块
STATS_GENERATING = - 正在生成: §2%s§r
SAVING_CHUNKS = 正在保存 %d 已载入的区块...
CANCEL_FAIL = 取消任务 #%d 操作超时!
NO_AUTOSTART = 自动启动被设置为 §2关闭§r. 正在暂停...

@ -1,7 +1,7 @@
main: net.trivernis.chunkmaster.Chunkmaster main: net.trivernis.chunkmaster.Chunkmaster
name: Chunkmaster name: Chunkmaster
version: $$PLUGIN_VERSION$$ version: '1.2.1'
description: Automated world pregeneration. description: Chunk commands plugin.
author: Trivernis author: Trivernis
website: trivernis.net website: trivernis.net
api-version: '1.14' api-version: '1.14'
@ -23,20 +23,19 @@ commands:
/<command> setCenter [[<world>] <chunkX> <chunkZ>]] - sets the center chunk of the world /<command> setCenter [[<world>] <chunkX> <chunkZ>]] - sets the center chunk of the world
/<command> getCenter [<world>] - returns 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> stats [<world>] - returns some chunk stats for the world or the whole server
/<command> completed - lists all completed tasks for all worlds
aliases: aliases:
- chm - chm
- chunkm - chunkm
- cmaster - cmaster
permissions: permissions:
chunkmaster.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 cancel subcommand. description: Allows the remove subcommand.
default: op default: op
chunkmaster.pause: chunkmaster.pause:
description: Allows the pause subcommand. description: Allows the pause subcommand.
@ -56,12 +55,6 @@ permissions:
chunkmaster.getcenter: chunkmaster.getcenter:
description: Allows the getCenter subcommand. description: Allows the getCenter subcommand.
default: op default: op
chunkmaster.stats:
description: Allows the stats subcommand.
deault: op
chunkmaster.completed:
description: Allows the completed subcommand.
default: op
chunkmaster.chunkmaster: chunkmaster.chunkmaster:
description: Allows Chunkmaster commands. description: Allows Chunkmaster commands.
default: op default: op
@ -70,14 +63,8 @@ permissions:
default: op default: op
children: children:
- chunkmaster.generate - chunkmaster.generate
- chunkmaster.list - chunkmaster.listgentasks
- chunkmaster.cancel - chunkmaster.removegentask
- chunkmaster.pause - chunkmaster.pausegentasks
- chunkmaster.resume - chunkmaster.resumegentasks
- chunkmaster.completed
- chunkmaster.tpchunk
- chunkmaster.reload
- chunkmaster.setcenter
- chunkmaster.getcenter
- chunkmaster.stats
- chunkmaster.chunkmaster - chunkmaster.chunkmaster

@ -1,60 +0,0 @@
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())
}
}

@ -1,29 +0,0 @@
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()
}
}

@ -1,75 +0,0 @@
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)
}
}

@ -1,62 +0,0 @@
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