First public release
parent
93080246f6
commit
998c758df5
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
steps:
|
||||
- name: "Build"
|
||||
image: "eclipse-temurin:17-jdk"
|
||||
commands:
|
||||
- "./gradlew assemble"
|
||||
- name: "Test"
|
||||
image: "eclipse-temurin:17-jdk"
|
||||
commands:
|
||||
- "./gradlew test"
|
||||
|
||||
- name: "Create Release"
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: "gitea_api_key"
|
||||
base_url:
|
||||
from_secret: "gitea_base_url"
|
||||
files: build/libs/DLib-*.jar
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
- name: "Publish to Maven"
|
||||
image: "eclipse-temurin:17-jdk"
|
||||
commands:
|
||||
- "./gradlew publishReleasePublicationToMavenRepository"
|
||||
environment:
|
||||
MAVEN_REPO_URL:
|
||||
from_secret: maven_repo_url
|
||||
MAVEN_REPO_TOKEN:
|
||||
from_secret: gitea_api_key
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
- tag
|
|
@ -1,3 +1,7 @@
|
|||
build/
|
||||
.idea/
|
||||
.gradle/
|
||||
|
||||
# ---> Kotlin
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
@ -23,3 +27,4 @@
|
|||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.7.0"
|
||||
kotlin("plugin.serialization") version "1.7.0"
|
||||
id("maven-publish")
|
||||
id("com.github.johnrengelman.shadow") version "7.1.2"
|
||||
id("io.papermc.paperweight.userdev") version "1.3.5"
|
||||
}
|
||||
|
||||
group = "nl.kallestruik"
|
||||
version = "1.5.0"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
paperDevBundle("1.19-R0.1-SNAPSHOT")
|
||||
|
||||
implementation(kotlin("reflect"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm-host")
|
||||
|
||||
testImplementation(kotlin("test-junit5"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
assemble {
|
||||
dependsOn(reobfJar)
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
withType<KotlinCompile> {
|
||||
kotlinOptions.jvmTarget = "17"
|
||||
}
|
||||
|
||||
processResources {
|
||||
expand("version" to project.version)
|
||||
}
|
||||
}
|
||||
|
||||
val sourcesJar by tasks.creating(Jar::class) {
|
||||
archiveClassifier.set("sources")
|
||||
from(sourceSets["main"].allSource)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("release") {
|
||||
from(components["kotlin"])
|
||||
artifact(sourcesJar)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = uri(System.getenv("MAVEN_REPO_URL") ?: "")
|
||||
|
||||
credentials(HttpHeaderCredentials::class) {
|
||||
name = "Authorization"
|
||||
value = "token ${System.getenv("MAVEN_REPO_TOKEN")}"
|
||||
}
|
||||
|
||||
authentication {
|
||||
create<HttpHeaderAuthentication>("Authentication Header")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.optIn("kotlin.RequiresOptIn")
|
||||
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
kotlin.code.style=official
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,185 @@
|
|||
#!/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
|
||||
;;
|
||||
MSYS* | 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" "$@"
|
|
@ -0,0 +1,89 @@
|
|||
@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
|
|
@ -0,0 +1,9 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven("https://papermc.io/repo/repository/maven-public/")
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "DLib"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package nl.kallestruik.dlib
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
|
||||
/**
|
||||
* This class is only here to make sure that paper loads the plugin.
|
||||
*/
|
||||
class DLib: JavaPlugin()
|
|
@ -0,0 +1,45 @@
|
|||
package nl.kallestruik.dlib
|
||||
|
||||
import org.bukkit.util.Vector
|
||||
import java.util.*
|
||||
|
||||
class DUtil(
|
||||
private val random: Random
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a random vector with integer offsets within the min and max.
|
||||
* @param min The minimum offset.
|
||||
* @param max The maximum offset.
|
||||
* @param height Whether the height should also have a random offset.
|
||||
* @return A [Vector] containing the offsets.
|
||||
*/
|
||||
fun getRandomLocationOffset(min: Int, max: Int, height: Boolean): Vector {
|
||||
// Create the vectors with all positive numbers.
|
||||
val vec = if (height) {
|
||||
Vector(
|
||||
min + random.nextInt(max - min),
|
||||
min + random.nextInt(max - min),
|
||||
min + random.nextInt(max - min)
|
||||
)
|
||||
} else {
|
||||
Vector(
|
||||
min + random.nextInt(max - min),
|
||||
0,
|
||||
min + random.nextInt(max - min)
|
||||
)
|
||||
}
|
||||
|
||||
// Randomly make some components negative instead of positive.
|
||||
vec.multiply(
|
||||
Vector(
|
||||
if (random.nextBoolean()) -1 else 1,
|
||||
if (random.nextBoolean()) -1 else 1,
|
||||
if (random.nextBoolean()) -1 else 1
|
||||
)
|
||||
)
|
||||
|
||||
// Return the completed vector.
|
||||
return vec
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package nl.kallestruik.dlib
|
||||
|
||||
class MathHelper {
|
||||
|
||||
/**
|
||||
* Clamp an integer between a min and a max value.
|
||||
* @param value The value to clamp.
|
||||
* @param min The minimal value.
|
||||
* @param max The maximum value.
|
||||
*/
|
||||
fun clamp(value: Int, min: Int, max: Int): Int {
|
||||
return if (value < min) min else value.coerceAtMost(max)
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a value to between 0 15 (inclusive) wrapping around to 15 at negative values.
|
||||
* @param value The value.
|
||||
* @return A positive int between 0 and 15 (inclusive).
|
||||
*/
|
||||
fun chunkAbs(value: Int): Int {
|
||||
return if (value >= 0)
|
||||
value % 16
|
||||
else
|
||||
16 + (value % 16)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package nl.kallestruik.dlib
|
||||
|
||||
import java.lang.reflect.Field
|
||||
|
||||
class ReflectionUtil {
|
||||
|
||||
/**
|
||||
* Get a {@link Field} from the class provided.
|
||||
* @param clazz The class to get the field from.
|
||||
* @param fieldName The name of the field to get.
|
||||
*/
|
||||
@Throws(NoSuchFieldException::class)
|
||||
fun getField(clazz: Class<*>, fieldName: String): Field {
|
||||
return try {
|
||||
clazz.getDeclaredField(fieldName)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
val superClass = clazz.superclass
|
||||
superClass?.let { getField(it, fieldName) } ?: throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value from a field on an object.
|
||||
* @param instance The instance of the object to take the value from.
|
||||
* @param fieldName The name of the field to take the value from.
|
||||
*/
|
||||
fun getValueFromField(instance: Any, fieldName: String): Any? {
|
||||
try {
|
||||
val field = getField(instance.javaClass, fieldName)
|
||||
field.isAccessible = true //required if field is not normally accessible
|
||||
return field.get(instance)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
package nl.kallestruik.dlib.config
|
||||
|
||||
import kotlinx.serialization.descriptors.*
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import nl.kallestruik.dlib.config.annotations.*
|
||||
import nl.kallestruik.dlib.config.data.ConfigSchema
|
||||
import nl.kallestruik.dlib.config.data.ConfigValueConstraints
|
||||
import nl.kallestruik.dlib.config.data.ConfigValueType
|
||||
import nl.kallestruik.dlib.exceptions.ConfigSchemaGenerationException
|
||||
import kotlin.math.min
|
||||
import kotlin.math.max
|
||||
|
||||
fun SerialDescriptor.toConfigSchema(annotations: List<Annotation> = listOf()): ConfigSchema {
|
||||
val allAnnotations = annotations + this.annotations
|
||||
return when (this.kind) {
|
||||
SerialKind.ENUM -> createEnumConfigSchema(this, allAnnotations)
|
||||
PrimitiveKind.BOOLEAN -> createBooleanConfigSchema(allAnnotations)
|
||||
PrimitiveKind.BYTE -> createIntegerConfigSchema(allAnnotations, Byte.MAX_VALUE.toLong(), Byte.MIN_VALUE.toLong())
|
||||
PrimitiveKind.CHAR -> createStringConfigSchema(allAnnotations, maxLength=1)
|
||||
PrimitiveKind.SHORT -> createIntegerConfigSchema(allAnnotations, Short.MAX_VALUE.toLong(), Short.MIN_VALUE.toLong())
|
||||
PrimitiveKind.INT -> createIntegerConfigSchema(allAnnotations, Int.MAX_VALUE.toLong(), Int.MIN_VALUE.toLong())
|
||||
PrimitiveKind.LONG -> createIntegerConfigSchema(allAnnotations, Long.MAX_VALUE, Long.MIN_VALUE)
|
||||
PrimitiveKind.FLOAT -> createFloatConfigSchema(allAnnotations, Float.MAX_VALUE.toDouble(), -Float.MAX_VALUE.toDouble())
|
||||
PrimitiveKind.DOUBLE -> createFloatConfigSchema(allAnnotations, Double.MAX_VALUE, -Double.MAX_VALUE)
|
||||
PrimitiveKind.STRING -> createStringConfigSchema(allAnnotations)
|
||||
StructureKind.LIST -> createListConfigSchema(this, allAnnotations)
|
||||
StructureKind.MAP -> createMapConfigSchema(this, allAnnotations)
|
||||
StructureKind.OBJECT, StructureKind.CLASS -> createObjectConfigSchema(this, allAnnotations)
|
||||
else -> throw ConfigSchemaGenerationException("Creating a schema for type ${this.kind} is not currently supported.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createObjectConfigSchema(descriptor: SerialDescriptor, annotations: List<Annotation>): ConfigSchema {
|
||||
val description = annotations
|
||||
.filterIsInstance<Description>()
|
||||
.flatMap { it.lines.toList() }
|
||||
|
||||
val children = descriptor.elementDescriptors
|
||||
.mapIndexed { i, child ->
|
||||
descriptor.getElementName(i) to child.toConfigSchema(descriptor.getElementAnnotations(i))
|
||||
}.toMap()
|
||||
|
||||
return ConfigSchema(
|
||||
type = ConfigValueType.OBJECT,
|
||||
children = children,
|
||||
description = description,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createListConfigSchema(descriptor: SerialDescriptor, annotations: List<Annotation>): ConfigSchema {
|
||||
val description = annotations
|
||||
.filterIsInstance<Description>()
|
||||
.flatMap { it.lines.toList() }
|
||||
|
||||
return ConfigSchema(
|
||||
type = ConfigValueType.LIST,
|
||||
description = description,
|
||||
items = descriptor.elementDescriptors.first().toConfigSchema(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMapConfigSchema(descriptor: SerialDescriptor, annotations: List<Annotation>): ConfigSchema {
|
||||
val description = annotations
|
||||
.filterIsInstance<Description>()
|
||||
.flatMap { it.lines.toList() }
|
||||
|
||||
val allowedKeys = annotations
|
||||
.filterIsInstance<AllowedKeys>()
|
||||
.flatMap { annotation -> annotation.keys
|
||||
.map { listOf(it) }
|
||||
}
|
||||
|
||||
val allowedKeysExclusive = annotations
|
||||
.filterIsInstance<AllowedKeysExclusive>()
|
||||
.map { it.exclusiveKeys.toList() }
|
||||
|
||||
if (descriptor.elementDescriptors.first().kind != PrimitiveKind.STRING) {
|
||||
throw ConfigSchemaGenerationException("Map keys have to be strings.")
|
||||
}
|
||||
|
||||
return ConfigSchema(
|
||||
type = ConfigValueType.MAP,
|
||||
description = description,
|
||||
items = descriptor.elementDescriptors.last().toConfigSchema(),
|
||||
constraints = ConfigValueConstraints(
|
||||
allowedKeys = allowedKeys + allowedKeysExclusive,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createStringConfigSchema(annotations: List<Annotation>, maxLength: Int? = null): ConfigSchema {
|
||||
val description = annotations
|
||||
.filterIsInstance<Description>()
|
||||
.flatMap { it.lines.toList() }
|
||||
|
||||
val allowedValues = annotations
|
||||
.filterIsInstance<StringEnum>()
|
||||
.flatMap { it.values.toList() }
|
||||
|
||||
val default = annotations
|
||||
.filterIsInstance<DefaultString>()
|
||||
.firstOrNull()
|
||||
?.let { JsonPrimitive(it.default) }
|
||||
|
||||
return ConfigSchema(
|
||||
type = ConfigValueType.STRING,
|
||||
description = description,
|
||||
default = default,
|
||||
constraints = ConfigValueConstraints(
|
||||
enum = allowedValues.ifEmpty { null },
|
||||
maxLength = maxLength,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createEnumConfigSchema(descriptor: SerialDescriptor, annotations: List<Annotation>): ConfigSchema {
|
||||
val description = annotations
|
||||
.filterIsInstance<Description>()
|
||||
.flatMap { it.lines.toList() }
|
||||
|
||||
val allowedValues = descriptor.elementNames.toList()
|
||||
|
||||
val default = annotations
|
||||
.filterIsInstance<DefaultString>()
|
||||
.firstOrNull()
|
||||
?.let { JsonPrimitive(it.default) }
|
||||
|
||||
return ConfigSchema(
|
||||
type = ConfigValueType.STRING,
|
||||
description = description,
|
||||
default = default,
|
||||
constraints = ConfigValueConstraints(
|
||||
enum = allowedValues.ifEmpty { null },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createBooleanConfigSchema(annotations: List<Annotation>): ConfigSchema {
|
||||
val description = annotations
|
||||
.filterIsInstance<Description>()
|
||||
.flatMap { it.lines.toList() }
|
||||
|
||||
val default = annotations
|
||||
.filterIsInstance<DefaultBoolean>()
|
||||
.firstOrNull()
|
||||
?.let { JsonPrimitive(it.default) }
|
||||
|
||||
return ConfigSchema(
|
||||
type = ConfigValueType.BOOLEAN,
|
||||
description = description,
|
||||
default = default,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createIntegerConfigSchema(annotations: List<Annotation>, maxValue: Long, minValue: Long): ConfigSchema {
|
||||
val description = annotations
|
||||
.filterIsInstance<Description>()
|
||||
.flatMap { it.lines.toList() }
|
||||
|
||||
val max = annotations
|
||||
.filterIsInstance<MaxInt>()
|
||||
.map { it.max }
|
||||
.reduceOrNull { acc, next ->
|
||||
return@reduceOrNull max(acc, next)
|
||||
} ?: maxValue
|
||||
|
||||
val min = annotations
|
||||
.filterIsInstance<MinInt>()
|
||||
.map { it.min }
|
||||
.reduceOrNull { acc, next ->
|
||||
min(acc, next)
|
||||
} ?: minValue
|
||||
|
||||
val default = annotations
|
||||
.filterIsInstance<DefaultInt>()
|
||||
.firstOrNull()
|
||||
?.let { JsonPrimitive(it.default) }
|
||||
|
||||
return ConfigSchema(
|
||||
type = ConfigValueType.INTEGER,
|
||||
description = description,
|
||||
default = default,
|
||||
constraints = ConfigValueConstraints(
|
||||
maxValueInt = max,
|
||||
minValueInt = min,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFloatConfigSchema(annotations: List<Annotation>, maxValue: Double, minValue: Double): ConfigSchema {
|
||||
val description = annotations
|
||||
.filterIsInstance<Description>()
|
||||
.flatMap { it.lines.toList() }
|
||||
|
||||
val max = annotations
|
||||
.filterIsInstance<MaxFloat>()
|
||||
.map { it.max }
|
||||
.reduceOrNull { acc, next ->
|
||||
return@reduceOrNull max(acc, next)
|
||||
} ?: maxValue
|
||||
|
||||
val min = annotations
|
||||
.filterIsInstance<MinFloat>()
|
||||
.map { it.min }
|
||||
.reduceOrNull { acc, next ->
|
||||
min(acc, next)
|
||||
} ?: minValue
|
||||
|
||||
val default = annotations
|
||||
.filterIsInstance<DefaultFloat>()
|
||||
.firstOrNull()
|
||||
?.let { JsonPrimitive(it.default) }
|
||||
|
||||
return ConfigSchema(
|
||||
type = ConfigValueType.FLOAT,
|
||||
description = description,
|
||||
default = default,
|
||||
constraints = ConfigValueConstraints(
|
||||
maxValueFloat = max,
|
||||
minValueFloat = min,
|
||||
),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Repeatable
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class AllowedKeys(
|
||||
vararg val keys: String,
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Repeatable
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class AllowedKeysExclusive(
|
||||
vararg val exclusiveKeys: String,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DefaultBoolean(
|
||||
val default: Boolean,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DefaultFloat(
|
||||
val default: Double,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DefaultInt(
|
||||
val default: Long,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DefaultString(
|
||||
val default: String,
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Repeatable
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Description(
|
||||
vararg val lines: String,
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
import nl.kallestruik.dlib.config.data.ConfigDisplayType
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DisplayType(
|
||||
val type: ConfigDisplayType
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class MaterialField(
|
||||
val field: String,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class MaxFloat(
|
||||
val max: Double,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class MaxInt(
|
||||
val max: Long,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class MinFloat(
|
||||
val min: Double,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class MinInt(
|
||||
val min: Long,
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
import org.bukkit.Material
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class StaticMaterial(
|
||||
val material: Material,
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package nl.kallestruik.dlib.config.annotations
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
@SerialInfo
|
||||
@Repeatable
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class StringEnum(
|
||||
vararg val values: String,
|
||||
)
|
|
@ -0,0 +1,20 @@
|
|||
package nl.kallestruik.dlib.config.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ConfigDisplaySettings(
|
||||
val type: ConfigDisplayType = ConfigDisplayType.DEFAULT,
|
||||
val materialField: String? = null,
|
||||
val staticMaterial: String? = null,
|
||||
val amountField: String? = null,
|
||||
val staticAmount: Int? = null,
|
||||
val inventorySize: Int? = null,
|
||||
)
|
||||
|
||||
enum class ConfigDisplayType {
|
||||
DEFAULT,
|
||||
CHECKLIST,
|
||||
ITEM,
|
||||
INVENTORY,
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package nl.kallestruik.dlib.config.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
@Serializable
|
||||
data class ConfigSchema(
|
||||
val type: ConfigValueType,
|
||||
val description: List<String>? = null,
|
||||
val children: Map<String, ConfigSchema>? = null,
|
||||
val items: ConfigSchema? = null,
|
||||
val default: JsonPrimitive? = null,
|
||||
val constraints: ConfigValueConstraints = ConfigValueConstraints(),
|
||||
val display: ConfigDisplaySettings? = null,
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
package nl.kallestruik.dlib.config.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ConfigValueConstraints(
|
||||
val enum: List<String>? = null,
|
||||
val allowedKeys: List<List<String>>? = null,
|
||||
val maxLength: Int? = null,
|
||||
val maxValueInt: Long? = null,
|
||||
val minValueInt: Long? = null,
|
||||
val maxValueFloat: Double? = null,
|
||||
val minValueFloat: Double? = null,
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package nl.kallestruik.dlib.config.data
|
||||
|
||||
enum class ConfigValueType {
|
||||
STRING,
|
||||
MAP,
|
||||
LIST,
|
||||
OBJECT,
|
||||
BOOLEAN,
|
||||
INTEGER,
|
||||
FLOAT
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package nl.kallestruik.dlib.exceptions
|
||||
|
||||
class ConfigSchemaGenerationException(message: String = ""): RuntimeException(message)
|
|
@ -0,0 +1,222 @@
|
|||
package nl.kallestruik.dlib.gui
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.kyori.adventure.text.Component
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.HandlerList
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.inventory.ClickType
|
||||
import org.bukkit.event.inventory.InventoryAction
|
||||
import org.bukkit.event.inventory.InventoryClickEvent
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent
|
||||
import org.bukkit.inventory.InventoryView
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.plugin.Plugin
|
||||
|
||||
fun chestGUI(player: Player, plugin: Plugin, configure: suspend ChestGUI.() -> Unit) {
|
||||
val chestGUI = ChestGUI()
|
||||
runBlocking {
|
||||
chestGUI.configure()
|
||||
}
|
||||
|
||||
chestGUI.display(player, plugin)
|
||||
}
|
||||
|
||||
class ChestGUI: Listener {
|
||||
private data class Configuration(
|
||||
var border: (suspend ChestGUIEntry.() -> Unit)? = null,
|
||||
var content: MutableList<suspend ChestGUIEntry.() -> Unit> = mutableListOf(),
|
||||
var itemStacks: MutableMap<Int, ItemStack> = mutableMapOf(),
|
||||
var onClose: suspend (gui: ChestGUI, player: Player, event: InventoryCloseEvent) -> Unit = { _, _, _ ->}
|
||||
)
|
||||
|
||||
private val configuration = Configuration()
|
||||
var title: Component = Component.empty()
|
||||
var rows: Int = 0
|
||||
var cancelInteraction: Boolean = true
|
||||
|
||||
private lateinit var view: InventoryView
|
||||
private lateinit var entries: Array<ChestGUIEntry?>
|
||||
|
||||
|
||||
fun onClose(onClose: suspend (gui: ChestGUI, player: Player, event: InventoryCloseEvent) -> Unit) {
|
||||
configuration.onClose = onClose
|
||||
}
|
||||
|
||||
fun entry(entry: suspend ChestGUIEntry.() -> Unit) {
|
||||
configuration.content.add(entry)
|
||||
}
|
||||
|
||||
fun border(border: suspend ChestGUIEntry.() -> Unit) {
|
||||
configuration.border = border
|
||||
}
|
||||
|
||||
fun itemStack(slot: Int, itemStack: ItemStack) {
|
||||
configuration.itemStacks[slot] = itemStack
|
||||
}
|
||||
|
||||
fun display(player: Player, plugin: Plugin) {
|
||||
val inventory = Bukkit.createInventory(null, rows * 9, title)
|
||||
view = player.openInventory(inventory) ?: return
|
||||
|
||||
entries = arrayOfNulls(rows * 9)
|
||||
|
||||
if (configuration.border != null) {
|
||||
val border = ChestGUIEntry()
|
||||
runBlocking {
|
||||
configuration.border!!.invoke(border)
|
||||
}
|
||||
|
||||
val borderStack = border.toItemStack()
|
||||
|
||||
// Top/Bottom border
|
||||
for (x in 0..8) {
|
||||
view.setItem(x, borderStack)
|
||||
view.setItem((rows - 1) * 9 + x, borderStack)
|
||||
}
|
||||
|
||||
// Middle side border
|
||||
for (y in 1..rows - 2) {
|
||||
view.setItem(9 * y, borderStack)
|
||||
view.setItem(9 * y + 8, borderStack)
|
||||
}
|
||||
}
|
||||
|
||||
configuration.content.forEach {
|
||||
val entry = ChestGUIEntry()
|
||||
runBlocking {
|
||||
it.invoke(entry)
|
||||
}
|
||||
|
||||
if (entry.getSlot() < 0)
|
||||
return@forEach
|
||||
|
||||
entries[entry.getSlot()] = entry
|
||||
}
|
||||
|
||||
entries.forEach { entry ->
|
||||
if (entry == null)
|
||||
return@forEach
|
||||
|
||||
if (entry.x != -1 || entry.y != -1)
|
||||
return@forEach
|
||||
|
||||
inventory.setItem(entry.getSlot(), entry.toItemStack())
|
||||
}
|
||||
|
||||
configuration.itemStacks.forEach { (slot, itemStack) ->
|
||||
inventory.setItem(slot, itemStack)
|
||||
}
|
||||
|
||||
plugin.server.pluginManager.registerEvents(this, plugin)
|
||||
}
|
||||
|
||||
private fun cleanup() {
|
||||
HandlerList.unregisterAll(this)
|
||||
}
|
||||
|
||||
/*
|
||||
Bukkit event handlers
|
||||
*/
|
||||
@EventHandler
|
||||
fun onInventoryClose(event: InventoryCloseEvent) {
|
||||
if (event.view != this.view)
|
||||
return
|
||||
|
||||
cleanup()
|
||||
|
||||
if (event.reason == InventoryCloseEvent.Reason.PLUGIN) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.player !is Player)
|
||||
return
|
||||
|
||||
runBlocking {
|
||||
configuration.onClose(this@ChestGUI, event.player as Player, event)
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onClick(event: InventoryClickEvent) {
|
||||
if (event.view != view) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.action != InventoryAction.PICKUP_ALL) {
|
||||
if (cancelInteraction)
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
val slot = view.convertSlot(event.rawSlot)
|
||||
|
||||
if (cancelInteraction)
|
||||
event.isCancelled = true
|
||||
|
||||
val entry = entries[slot] ?: return
|
||||
|
||||
if (event.whoClicked !is Player)
|
||||
return
|
||||
|
||||
runBlocking {
|
||||
when (event.click) {
|
||||
ClickType.LEFT -> entry.onLeftClick(event)
|
||||
ClickType.MIDDLE -> entry.onMiddleClick(event)
|
||||
ClickType.RIGHT -> entry.onRightClick(event)
|
||||
else -> entry.onOtherClick(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ChestGUIEntry(
|
||||
var name: Component = Component.empty(),
|
||||
var material: Material = Material.AIR,
|
||||
var amount: Int = 1,
|
||||
var lore: MutableList<Component> = mutableListOf(),
|
||||
var x: Int = -1,
|
||||
var y: Int = -1,
|
||||
var onLeftClick: suspend (event: InventoryClickEvent) -> Unit = {},
|
||||
var onMiddleClick: suspend (event: InventoryClickEvent) -> Unit = {},
|
||||
var onRightClick: suspend (event: InventoryClickEvent) -> Unit = {},
|
||||
var onOtherClick: suspend (event: InventoryClickEvent) -> Unit = {},
|
||||
) {
|
||||
|
||||
fun onLeftClick(onLeftClick: suspend (event: InventoryClickEvent) -> Unit) {
|
||||
this.onLeftClick = onLeftClick
|
||||
}
|
||||
|
||||
fun onMiddleClick(onMiddleClick: suspend (event: InventoryClickEvent) -> Unit) {
|
||||
this.onMiddleClick = onMiddleClick
|
||||
}
|
||||
|
||||
fun onRightClick(onRightClick: suspend (event: InventoryClickEvent) -> Unit) {
|
||||
this.onRightClick = onRightClick
|
||||
}
|
||||
|
||||
fun onOtherClick(onOtherClick: suspend (event: InventoryClickEvent) -> Unit) {
|
||||
this.onOtherClick = onOtherClick
|
||||
}
|
||||
|
||||
fun lore(line: Component) {
|
||||
lore.add(line)
|
||||
}
|
||||
|
||||
fun getSlot(): Int {
|
||||
return x + y * 9
|
||||
}
|
||||
|
||||
fun toItemStack(): ItemStack {
|
||||
val itemStack = ItemStack(material, amount)
|
||||
val itemMeta = itemStack.itemMeta
|
||||
itemMeta.displayName(name)
|
||||
itemMeta.lore(lore)
|
||||
|
||||
itemStack.itemMeta = itemMeta
|
||||
return itemStack
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package nl.kallestruik.dlib.gui
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.HandlerList
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.inventory.InventoryAction
|
||||
import org.bukkit.event.inventory.InventoryClickEvent
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent
|
||||
import org.bukkit.inventory.InventoryView
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.plugin.Plugin
|
||||
|
||||
fun confirmationDialog(player: Player, plugin: Plugin, configure: suspend ConfirmationDialog.() -> Unit) {
|
||||
val confirmationDialog = ConfirmationDialog()
|
||||
runBlocking {
|
||||
confirmationDialog.configure()
|
||||
}
|
||||
|
||||
confirmationDialog.display(player, plugin)
|
||||
}
|
||||
|
||||
class ConfirmationDialog: Listener {
|
||||
private data class Configuration(
|
||||
var onConfirm: suspend () -> Unit = {},
|
||||
var onCancel: suspend () -> Unit = {}
|
||||
)
|
||||
|
||||
var title: Component = Component.empty()
|
||||
var description: Component = Component.empty()
|
||||
|
||||
private val configuration = Configuration()
|
||||
private lateinit var plugin: Plugin
|
||||
private lateinit var view: InventoryView
|
||||
|
||||
fun onConfirm(onConfirm: suspend () -> Unit) {
|
||||
configuration.onConfirm = onConfirm
|
||||
}
|
||||
|
||||
fun onCancel(onCancel: suspend () -> Unit) {
|
||||
configuration.onCancel = onCancel
|
||||
}
|
||||
|
||||
fun display(player: Player, plugin: Plugin) {
|
||||
player.closeInventory()
|
||||
this.plugin = plugin
|
||||
|
||||
val inventory = Bukkit.createInventory(null, 3 * 9, title)
|
||||
view = player.openInventory(inventory) ?: return
|
||||
|
||||
val borderStack = ItemStack(Material.ORANGE_STAINED_GLASS_PANE, 1)
|
||||
val borderMeta = borderStack.itemMeta
|
||||
borderMeta.displayName(Component.empty())
|
||||
borderStack.itemMeta = borderMeta
|
||||
|
||||
// Top border
|
||||
for (x in 8 downTo 0) {
|
||||
view.setItem(x, borderStack)
|
||||
}
|
||||
|
||||
// Middle sides
|
||||
view.setItem(9, borderStack)
|
||||
view.setItem(17, borderStack)
|
||||
|
||||
// Bottom border
|
||||
for (x in 26 downTo 18) {
|
||||
view.setItem(x, borderStack)
|
||||
}
|
||||
|
||||
// Description item
|
||||
val descriptionItem = ItemStack(Material.PAPER, 1)
|
||||
val descriptionMeta = descriptionItem.itemMeta
|
||||
descriptionMeta.displayName(description)
|
||||
descriptionItem.itemMeta = descriptionMeta
|
||||
|
||||
// Confirm item
|
||||
val confirmItem = ItemStack(Material.SUNFLOWER, 1)
|
||||
val confirmMeta = confirmItem.itemMeta
|
||||
confirmMeta.displayName(Component.text("Confirm").color(NamedTextColor.GREEN))
|
||||
confirmItem.itemMeta = confirmMeta
|
||||
|
||||
// Cancel item
|
||||
val cancelItem = ItemStack(Material.BARRIER, 1)
|
||||
val cancelMeta = cancelItem.itemMeta
|
||||
cancelMeta.displayName(Component.text("Cancel").color(NamedTextColor.RED))
|
||||
cancelItem.itemMeta = cancelMeta
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// 9 10 11 12 13 14 15 16 17
|
||||
// 18 19 20 21 22 23 24 25 26
|
||||
|
||||
view.setItem(4, descriptionItem)
|
||||
view.setItem(11, confirmItem)
|
||||
view.setItem(15, cancelItem)
|
||||
|
||||
plugin.server.pluginManager.registerEvents(this, plugin)
|
||||
}
|
||||
|
||||
private fun cleanup() {
|
||||
view.close()
|
||||
HandlerList.unregisterAll(this)
|
||||
}
|
||||
|
||||
/*
|
||||
Bukkit event handlers
|
||||
*/
|
||||
@EventHandler
|
||||
fun onInventoryClose(event: InventoryCloseEvent) {
|
||||
if (event.view != this.view)
|
||||
return
|
||||
|
||||
if (event.reason == InventoryCloseEvent.Reason.PLUGIN) {
|
||||
return
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
configuration.onCancel.invoke()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onOperationFinish(event: InventoryClickEvent) {
|
||||
if (event.view != view) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.action != InventoryAction.PICKUP_ALL) {
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
val slot = view.convertSlot(event.rawSlot)
|
||||
|
||||
if (slot != 11 && slot != 15) {
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
event.isCancelled = true
|
||||
|
||||
if (slot == 11) {
|
||||
runBlocking {
|
||||
configuration.onConfirm()
|
||||
cleanup()
|
||||
}
|
||||
} else {
|
||||
runBlocking {
|
||||
configuration.onCancel()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package nl.kallestruik.dlib.gui
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.kyori.adventure.text.Component
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.HandlerList
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.inventory.*
|
||||
import org.bukkit.inventory.InventoryView
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.plugin.Plugin
|
||||
|
||||
fun textInputDialog(player: Player, plugin: Plugin, configure: suspend TextInputDialog.() -> Unit) {
|
||||
val textInputDialog = TextInputDialog()
|
||||
runBlocking {
|
||||
textInputDialog.configure()
|
||||
}
|
||||
|
||||
textInputDialog.display(player, plugin)
|
||||
}
|
||||
|
||||
class TextInputDialog: Listener {
|
||||
private data class Configuration(
|
||||
var item: suspend TextInputDialogItem.() -> Unit = {},
|
||||
var onFinish: suspend (value: String) -> Unit = {},
|
||||
var onCancel: suspend () -> Unit = {}
|
||||
)
|
||||
|
||||
private val configuration = Configuration()
|
||||
var prompt: Component = Component.empty()
|
||||
|
||||
private lateinit var view: InventoryView
|
||||
private lateinit var plugin: Plugin
|
||||
|
||||
|
||||
fun item(entry: suspend TextInputDialogItem.() -> Unit) {
|
||||
configuration.item = entry
|
||||
}
|
||||
|
||||
fun onFinish(onFinish: suspend (value: String) -> Unit) {
|
||||
configuration.onFinish = onFinish
|
||||
}
|
||||
|
||||
fun onCancel(onCancel: suspend () -> Unit) {
|
||||
configuration.onCancel = onCancel
|
||||
}
|
||||
|
||||
fun display(player: Player, plugin: Plugin) {
|
||||
player.closeInventory()
|
||||
this.plugin = plugin
|
||||
view = player.openAnvil(null, true) ?: return
|
||||
|
||||
val entry = TextInputDialogItem()
|
||||
runBlocking {
|
||||
configuration.item.invoke(entry)
|
||||
}
|
||||
|
||||
val itemStack = ItemStack(entry.material, entry.amount)
|
||||
val itemMeta = itemStack.itemMeta
|
||||
itemMeta.displayName(prompt)
|
||||
itemStack.itemMeta = itemMeta
|
||||
|
||||
view.setItem(0, itemStack)
|
||||
|
||||
plugin.server.pluginManager.registerEvents(this, plugin)
|
||||
}
|
||||
|
||||
private fun cleanup() {
|
||||
view.close()
|
||||
HandlerList.unregisterAll(this)
|
||||
}
|
||||
|
||||
/*
|
||||
Bukkit event handlers
|
||||
*/
|
||||
@EventHandler
|
||||
fun onInventoryClose(event: InventoryCloseEvent) {
|
||||
if (event.view != this.view)
|
||||
return
|
||||
|
||||
if (event.reason == InventoryCloseEvent.Reason.PLUGIN) {
|
||||
return
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
configuration.onCancel.invoke()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onAnvilPrepare(event: PrepareAnvilEvent) {
|
||||
if (event.view != view)
|
||||
return
|
||||
|
||||
// Always keep the cost at 1. 0 Causes the client to not be able to click it.
|
||||
event.inventory.repairCost = 1
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onOperationFinish(event: InventoryClickEvent) {
|
||||
if (event.view != view) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.action != InventoryAction.PICKUP_ALL) {
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
if (view.convertSlot(event.rawSlot) != 2) {
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
val item = event.currentItem
|
||||
val name = item?.itemMeta?.displayName ?: ""
|
||||
|
||||
if (name == "") {
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
event.isCancelled = true
|
||||
|
||||
runBlocking {
|
||||
configuration.onFinish.invoke(name)
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TextInputDialogItem(
|
||||
var material: Material = Material.AIR,
|
||||
var amount: Int = 1,
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
package nl.kallestruik.dlib.serializers
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.util.*
|
||||
|
||||
object UUIDSerializer: KSerializer<UUID> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("uuid", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): UUID {
|
||||
return UUID.fromString(decoder.decodeString())
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: UUID) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
name: "DLib"
|
||||
main: nl.kallestruik.dlib.DLib
|
||||
version: ${version}
|
||||
api-version: 1.16
|
|
@ -0,0 +1,161 @@
|
|||
package nl.kallestruik.dlib.config
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import nl.kallestruik.dlib.config.annotations.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
class ConfigSchemaBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `config scheme contains all descriptions`() {
|
||||
val schema = Company.serializer().descriptor.toConfigSchema()
|
||||
|
||||
assertEquals(
|
||||
listOf("The name of the company."),
|
||||
schema.children!!["name"]!!.description
|
||||
)
|
||||
assertEquals(
|
||||
listOf("The person that owns the company."),
|
||||
schema.children!!["owner"]!!.description
|
||||
)
|
||||
assertEquals(
|
||||
listOf("All the employees of the company.", "This list should also contain the owner for legacy reasons ;)."),
|
||||
schema.children!!["employees"]!!.description
|
||||
)
|
||||
|
||||
val personSchema = schema.children!!["owner"]!!
|
||||
|
||||
assertEquals(
|
||||
listOf("The list of qualifications that this person has."),
|
||||
personSchema.children!!["qualifications"]!!.description
|
||||
)
|
||||
assertEquals(
|
||||
listOf("The age of the person."),
|
||||
personSchema.children!!["age"]!!.description
|
||||
)
|
||||
assertEquals(
|
||||
listOf("Whether the person is currently employed at a company."),
|
||||
personSchema.children!!["isEmployed"]!!.description
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `config schema has correct defaults`() {
|
||||
val schema = Company.serializer().descriptor.toConfigSchema()
|
||||
val personSchema = schema.children!!["owner"]!!
|
||||
|
||||
assertEquals(JsonPrimitive("Bob"), personSchema.children!!["firstName"]!!.default)
|
||||
assertEquals(JsonPrimitive(18.0), personSchema.children!!["age"]!!.default)
|
||||
assertEquals(JsonPrimitive(true), personSchema.children!!["isEmployed"]!!.default)
|
||||
|
||||
val qualificationSchema = personSchema.children!!["qualifications"]!!.items!!
|
||||
|
||||
assertEquals(JsonPrimitive("EXPERIENCE"), qualificationSchema.children!!["type"]!!.default)
|
||||
assertEquals(JsonPrimitive(0), qualificationSchema.children!!["yearObtained"]!!.default)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `config schema allowed keys are correct`() {
|
||||
val schema = Company.serializer().descriptor.toConfigSchema()
|
||||
|
||||
assertEquals(
|
||||
listOf(listOf("Bob"), listOf("Joe"), listOf("Kon"), listOf("Jane", "Kevin")),
|
||||
schema.children!!["employees"]!!.constraints.allowedKeys
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `config schema enum values are correct`() {
|
||||
val schema = Company.serializer().descriptor.toConfigSchema()
|
||||
val personSchema = schema.children!!["owner"]!!
|
||||
|
||||
assertEquals(
|
||||
listOf("Bob", "Joe", "Kon", "Jane", "Kevin"),
|
||||
personSchema.children!!["firstName"]!!.constraints.enum
|
||||
)
|
||||
|
||||
val qualificationSchema = personSchema.children!!["qualifications"]!!.items!!
|
||||
|
||||
assertEquals(
|
||||
listOf("DIPLOMA", "EXPERIENCE", "CERTIFICATE"),
|
||||
qualificationSchema.children!!["type"]!!.constraints.enum
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `config schema has correct min and max values`() {
|
||||
val schema = Company.serializer().descriptor.toConfigSchema()
|
||||
val personSchema = schema.children!!["owner"]!!
|
||||
|
||||
assertEquals(
|
||||
0.0,
|
||||
personSchema.children!!["age"]!!.constraints.minValueFloat
|
||||
)
|
||||
assertEquals(
|
||||
75.0,
|
||||
personSchema.children!!["age"]!!.constraints.maxValueFloat
|
||||
)
|
||||
|
||||
val qualificationSchema = personSchema.children!!["qualifications"]!!.items!!
|
||||
|
||||
assertEquals(
|
||||
0,
|
||||
qualificationSchema.children!!["yearObtained"]!!.constraints.minValueInt
|
||||
)
|
||||
assertEquals(
|
||||
9999,
|
||||
qualificationSchema.children!!["yearObtained"]!!.constraints.maxValueInt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Company(
|
||||
@Description("The name of the company.")
|
||||
val name: String,
|
||||
@Description("The person that owns the company.")
|
||||
val owner: Person,
|
||||
@Description("All the employees of the company.", "This list should also contain the owner for legacy reasons ;).")
|
||||
@AllowedKeys("Bob", "Joe", "Kon") // Don't question it. I need to test stuff.
|
||||
@AllowedKeysExclusive("Jane", "Kevin")
|
||||
val employees: Map<String, Person>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Person(
|
||||
@DefaultString("Bob")
|
||||
@StringEnum("Bob", "Joe", "Kon", "Jane", "Kevin")
|
||||
val firstName: String,
|
||||
val lastName: String,
|
||||
@Description("The list of qualifications that this person has.")
|
||||
val qualifications: List<Qualification>,
|
||||
@DefaultFloat(18.0)
|
||||
@MinFloat(0.0)
|
||||
@MaxFloat(75.0)
|
||||
@Description("The age of the person.")
|
||||
val age: Float,
|
||||
@DefaultBoolean(true)
|
||||
@Description("Whether the person is currently employed at a company.")
|
||||
val isEmployed: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Qualification(
|
||||
val name: String,
|
||||
@DefaultString("EXPERIENCE")
|
||||
val type: QualificationType,
|
||||
@DefaultInt(0)
|
||||
@MinInt(0)
|
||||
@MaxInt(9999)
|
||||
val yearObtained: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class QualificationType {
|
||||
DIPLOMA,
|
||||
EXPERIENCE,
|
||||
CERTIFICATE,
|
||||
}
|
Loading…
Reference in New Issue