1.0.0.3 - new modules

Added utility, menu, thirdparty and exposed modules
A few little fixes
This commit is contained in:
redtea committed 2024-03-28 10:12:38 +03:00
1 parent 161f9aa268
commit 3c87e2d6a1
63 files changed
+5334 -12

No files matched your search

+89 -7
View File
@@ -1,15 +1,97 @@
# KotlinBukkitKit
KotlinBukkitKit is an API for Bukkit/SpigotAPI using features Kotlin. DevSrSouza / KotlinBukkitAPI.
KotlinBukkitKit is an API for Bukkit/SpigotAPI using features Kotlin.
Thanks for DevSrSouza / KotlinBukkitAPI.
```kotlin
implementation("tech.carcadex:kotlinbukkitkit-architecture:1.0.0.2")
implementation("tech.carcadex:kotlinbukkitkit-extensions:1.0.0.2")
implementation("tech.carcadex:kotlinbukkitkit-serialization:1.0.0.2")
implementation("tech.carcadex:kotlinbukkitkit-genref:1.0.0.2")
implementation("tech.carcadex:kotlinbukkitkit-messages:1.0.0.2")
implementation("tech.carcadex:kotlinbukkitkit-commands:1.0.0.2")
implementation("tech.carcadex:kotlinbukkitkit-architecture:1.0.0.3")
implementation("tech.carcadex:kotlinbukkitkit-extensions:1.0.0.3")
implementation("tech.carcadex:kotlinbukkitkit-serialization:1.0.0.3")
implementation("tech.carcadex:kotlinbukkitkit-messages:1.0.0.3")
implementation("tech.carcadex:kotlinbukkitkit-commands:1.0.0.3")
implementation("tech.carcadex:kotlinbukkitkit-utility:1.0.0.3")
implementation("tech.carcadex:kotlinbukkitkit-exposed:1.0.0.3")
compileOnly("tech.carcadex:kotlinbukkitkit-genref:1.0.0.3")
ksp("tech.carcadex:kotlinbukkitkit-genref:1.0.0.3")
```
Documentation will be there: https://red-tea.gitbook.io/kotlinbukkitkit/
<img src="https://github.com/DevSrSouza/KotlinBukkitAPI/raw/master/icon.png?size=96" alt="KotlinBukkitAPI" title="KotlinBukkitAPI" align="right"/>
## KotlinBukkitKit
![Kotlin version](https://img.shields.io/static/v1?label=Kotlin&message=1.9.23&color=Orange&style=for-the-badge)
[![GitHub stars](https://img.shields.io/github/stars/CarcadeX/KotlinBukkitKit.svg?style=for-the-badge&color=orange&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAADYklEQVR4XuWbvbPNQBiHn9uo6NFT0GEYHa0/QofOR2MojI8xxmh8VFwVf4RaxTB0KOhdPRUF8zPJmdy1J3l3N9ndxDb3zr0n2exzfu/zJjk5G5Qdh5vp35c6jI1SEzfzbgK/gXOljqMkgF3A1wbAXuBHCQglAehdf9wsWr8rDdlHSQCq+0MdBxzJvnqgFADJ752zYAHILsNSABT3Mw4A/S27DEsAaOW30wHwHcguwxIAuvJzyz67DEsA6MrPBaD/ZZVhbgA++bkQssowNwCf/FwAWWWYE8A6+bkAssowJ4A++RWTYU4AffIrJsNcACzyKyLDXAAs8isiwxwArPIrIsMcAELkl12GOQCEyC+7DAUgpj5LXLpPMeemAOwAXgLHp5ih4n2+Ak62JbCnuRmhn//D2ALUmre6DlAClAQlYsnjJ3ACeK1FuhI8CzxZ8uqbu06rG7C+LrBkKf5zpekDsFQp/pUeoBJYjXXnAUuT4kp6bnn3nQgtRYrbpBcCQK9dghR7b7RaToXnLMXB22sWAHOVold6oSXQvn5uUlwrvVgA2m4uUuyVXgqAuUgx6NMliwN8t6rcDzZrOXselF5qArR9rVI0SW8MANpHbVI0S28sADVJMUh6YwKoRYpB0hsbwEHgQ2ED6hg+xR5DTBfoznUDuB47+Ujb6Rhuxu4rFcBH4EDs5CNtp3dfKYgaKQBqiH+76OgySAFQQ/xbANFlkAKghvi3AKLLIBZATfFPKoNYADXFP6kMYgHUFP+kMogBUGP8o8sgBsBY8f8MXGqO/D6wP6qRb98ouBvEAEiNvx6DuwU8BH41x69L7AvANUBPlMSO4G4QCiAl/vpqzDPgKvBtzQp3A3eB0wmP8gedFIUCiI3/G+A88Nb41h4DHgFHja93r0/M1wahAELjr3f6CvC8+W5QyHp0bEqCEqFkWEdQGYQACIm/blI8AG4DqvmUISfIDXKE9dkFcxmEALDG/wVwEfiSsmrPtvsaqKcM+zV3gxAAQ/Fv25oATDkEYKhtmsvACqAv/r62NiUA7dvSNk1lYAXgi7+lrU0Noq9tmsrACsCNf2hbmxqEr22aysACoBv/lLY2NQRf2xwsAwsARUlnb2O1talBdNvmnaEbphYA94CnE7S1qUGobeozzMt9E/0BUX274HXoySkAAAAASUVORK5CYII=)](https://github.com/DevSrSouza/KotlinBukkitAPI/stargazers)
[![Github Issues](https://img.shields.io/github/issues-raw/CarcadeX/KotlinBukkitKit.svg?style=for-the-badge&color=orange&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAGfklEQVR4Xu2bd6glNRSHv7Vjw46KDRULCqLYFVSwIHZFFCvYKzZU7A17r6CsiqjYG4oigqIidtQ/RCzYOzbsDZUPMst9900myczc3bfuHnj/vElyzvklOTntTmIGp0kzuP7MBGDmCRg9ArMCawMbA6sCKwNLA/OFP7//BnwPfAa8C7wBvAC8BPw5ShFHdQXmALYF9gQ2B+ZvqcQvwBPAXcCDwO8t14lO6xuARYBjgEOAhXoW9jtgMnA58GVfa/cFwDzAacCRwNx9CRdZx+tyNXAu8GNXXn0AsBNwFbBUV2EK538BHA48UDhvzPAuAMwVFD+wiwA9zL05AOHJKKa2ALjbjwBrFHMczYTXge2BT0qXbwPAKsDjwDKlzEY8/vPw4rxVwqcUAJV/Bli0hAnwD/Ai8BTwCvAO4B3+GfgbmBdYIvgI6wCbARsAsxTyOQG4uGROCQAe++cKd/5j4BrgdsAdKqHFgb3D/V42Y+JZwJkZ41oZQQ2enlnunf8aOAW4BfirVKih8bMB+wHnAItF1kopXwEzDqDcE3A9cFCmIrcCRwXXNnNK1rAFw/uvdzlIOcqfESaMG5sDgO/8/RkiutN6gDclxv6b+J6SyWf3WmB2oET5iu2YOSlmGietasrJ+RXYObwOKay6AuD6WwIaS73BGHncq50fHjMFhBQAFwAnJjRy57fLVN6l+gAgBXKT8mNOQhMAPnUfZvj2BwA3piQa+N4FAI2x8/9oufPjTkITAB6vkxOKafD2KVC+ywlQeUNi/QavW12eIGfnxxjQGADG8zoqTSGtT53JjR+mAgCV8lsFXg8Buw49scXK6zfEABDh+xKKaY2Nz0up9AoMK1/xU77dw4lopbwLxQBwcUGIkR7eii2dnBIAYspXct0Z3OrTC3Yh+QzqeZl9MWcXo2Kfu4URTClfoPOUoVmO0HrB7Y0xMLAxqVnq21fr5Z4ADd4ObbSMzKl1muquwHHAJQ2Mnwc27CBYLgDbBA9Ug9yVoh5jHQAatv0bOJ4XAp22QuUC4Po7AvcAXsu21Ogu1wHwbMjhxxhqHLvk4UoAUAafuzsA6wellIoVal8Bvb+m+Ht14M1SSVoYwUEWewA6XSUJkqTyMqg7Ad8mHKCFwyvRFoPSE1Dx2RcwAZqKXxyfpXwMAF1MQ80YzdmxXNUWAOXR+TI30QRCtvLTIwDKbC3ANFsdpZQ/CTh/cOL0dAUG5T46lMgG/5dSXndZ8MYkdOsAmIhGsG639UYvDB9ylDc58gGwfOoEpJ7BXTJTZDEb0sUGDK9pPdLnsSkbPBgoPQ1smgIg5Qh5h1J5grYvRN/zhqPEG4CDUwAcC1zaIInpcYsWE53qQmRL91ekAMgJhiyL2c0xUSmWH7BT5dUUAN4pw+Gmrg4TpRdNUO1jytuCYwOH0ewUijkU9wIauxhZhV1hKiRESjFuygzdDew2vGAMgJxiiEUQvbJS6vMVGOSdSotZPn84FwBjcBMe+v0x+iYkRb0uJTQKAFLKK+uSdSe2yae2GHlqQjPDVCO1EuobgJTyylaUEKmUcfc/AmyAaqLSq9AnAIPeYEzGn0J4rxEcR6nQMqc4YqHC3N2jmcegLwC80yZmUjmCsxtqhMnY2pY3i6OpdhiLo2ZuckDoAwDzhabuDc2bSN9/tdCJWjsudQKcJNJWYlLkSTgi42XoCsChoTstJ08oUI2bkgOAil8HyDiHNIw2TJpZ6pMs09kXYDUoh+xdtFGjkXIB8KiZDl8ztWD4rvJGalaNuzY7m52yAu1d1pPLoZdDYjfJOxcAmfqO2iS1XI4EYcynYddskirt4bNrrGqSStmgQZHeBzbK7ScuAUAmKwHmC2LNSjFs9L9tj3syBCNvh+qzT5Q2wU4UAbbeuC6wCbB+hoUf5vdV2Pn3cjepFIAKBBslS05Crjxdxrnzls+zlZdZGwCq66BfvVYXiXuc6+myTae4jb4tAMquYbwMOKxHRdospbU/vq2x7QJAJazI27+f083ZRsHYHJ0c/Y4c5yvKtw8AXFyP0Zy76WoN2ihJw+mvRuxga9UiPyhcXwBUa+qs6Hx4LXLf7FywDGl1hK7sswu1bwAqZXRetgb2ArYAFsjVcmicEZw/mroNeCz0A7Vcqn7aqAAY5GaOUQ9y8GdzOja24FR5R3/749G290gfwQBMf+O14Rxer9p3eAb7lmOarTc1TsA0Uy6H8UwAclD6P4/5D0siPlDBJvVTAAAAAElFTkSuQmCC)](https://github.com/DevSrSouza/KotlinBukkitAPI/issues)
[![GitHub last commit](https://img.shields.io/github/last-commit/CarcadeX/KotlinBukkitKit/main.svg?style=for-the-badge&color=orange&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAABACAYAAABP97SyAAADZElEQVRoQ+2ZSciOURTHf1+ZZdoZMpdhIUOyJGTe2CpLyRhKYYGwQUghQ1nJ3sZQhpIQGbKhEMk8LMg8Rf/PfdT3ed/nTs/z9Xi7d/Uuzv+c8z//59577nmbaPDV1OD8SAT/d4WTgknBilcgfaIVF8iaXlLQWqKKGyQFKy6QNb2koLVEFTdICkYI1B2YA0wBxgKDgB7G3zvgIXATOAccB95HxKoLLUPBocA6YB7QxTHpT8BRYCvwwBHjZFYkwY7AZmAV0N4p+r9G34AdwCZAv6NXUQQHAseAMdEZ/XFwHZgLPIn1VwTBkWYf9Y5NphX+GTAZuBvjN5Zgf+AK0CcmiRysFJwAPA/1H0NQ++wSMD40uCPuMjAR+OFo38IshuAGcxiExPXF6FTWCeu9Qgn2A+4DnRwi6q47aPbpI2OvO1H34yJgtIOPj4Cun5cOtoUouBtYYQn2GVgOHM6xU4EXAvJnK9Z2YE1bENR99wLomRNM5GYAFxwTmgScspB8A/QFvjv6bDYL+URnm9YqL84Ci3K1sPpc91uSnw6c9iX4ywfgYKs9N87BrrWJii2sy550di+nRROUEjpUQtYSYF8IsB6mDILDgHuBSY4A7gRia8LKIKhDKLRRFvZLIuhRgTIUHB7RIKtxv+2Rv9W0DIKLgQPWyLUNlgF7ArFttgdvmRGF7+msYgs7qmiCvv5mAScsoJCrYimw1+J3GnDGJ+GQTkYnnd5nvXIC6SScCZx3TEYP25OAfNdbrwE1+aW3akpgl5m95OUvkiuBQznNhAqsy32nhZzibAPWOhbsr1mIggKr6dVzqbNDQO0rkdR4UKNCrSHAVPOScNlzei4J88ohXguTUIJyst5M0XxjhthLOSnovWIItgMumpmJd2APgGLoOfXTAxP9iWYOtOmvmk82JL4N89gUUO/PoBWjYBZQDbL2V9GTtadmbBjauDfnVwRB+RlgBr/6D6KIdc0MfkUyahVFUEl0ADYCq83vkMS+Apq9bPG97+oFK5JgFmOwGQ7NB7o6svwAHDEnZTZ5c4Tmm5VBMIvYDVBbl/19JuLZoOqtuRNvAGfNwEkkC19lEiw82RCHiWBI1aqESQpWSY2QXJKCIVWrEiYpWCU1QnJJCoZUrUqYpGCV1AjJJSkYUrUqYRpewd/nrH4569+7pgAAAABJRU5ErkJggg==)](https://github.com/DevSrSouza/KotlinBukkitAPI/commit)
[![Discord](https://img.shields.io/discord/1133859884685856878?style=for-the-badge&color=ORANGE&label=Discord)](https://discord.gg/pyrh4dUPZD)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?color=ORANGE&style=for-the-badge)](https://choosealicense.com/licenses/mit)
KotlinBukkitKit is an API for Bukkit/SpigotAPI using the cool and nifty features Kotlin has to make your life more easier.
### Contents:
* [Documentation](https://red-tea.gitbook.io/kotlinbukkitkit)
* [IDEA Plugin](https://github.com/CarcadeX/KotlinBukkitKit-Tooling)
# Samples
## [We have more Samples in the Documentation](https://red-tea.gitbook.io/kotlinbukkitkit)
KotlinBukkitAPI goes beyond this samples, and you can find all of it in the [wiki/documentation](https://red-tea.gitbook.io/kotlinbukkitkit).
Event DSL sample
```kotlin
plugin.events {
event<PlayerJoinEvent> {
player.msg("&3Welcome ${player.name}".translateColor())
}
event<PlayerQuitEvent> {
broadcast("&eThe player &c${player.name} &eleft :(".translateColor())
}
}
```
Simple Command DSL example
```kotlin
plugin.simpleCommand("hi") {
sender.msg("&eHallo".translateColor())
}
```
Item meta DSL and other stuff
```kotlin
val gem = item(Material.DIAMOND).apply {
amount = 5
meta<ItemMeta> {
displayName = "&bGem".translateColor()
}
}
val encbook = item(Material.ENCHANTED_BOOK).meta<EnchantmentStorageMeta> {
displayName = "&4&lThe powerful BOOK".translateColor()
addStoredEnchant(Enchantment.DAMAGE_ALL, 10, true) // putting sharpness 10 to the book
}
```
Another approach:
```kotlin
val gem = item(Material.DIAMOND, amount = 5).displayName("&bGem".translateColor())
val encbook = metadataItem<EnchantmentStorageMeta>(Material.ENCHANTED_BOOK) {
displayName = "&4&lThe powerful BOOK".translateColor()
addStoredEnchant(Enchantment.DAMAGE_ALL, 10, true) // putting sharpness 10 to the book
}
```
## IDEA Plugin - project wizard
You can find it on a <a href="https://plugins.jetbrains.com/plugin/24043-kotlinbukkitkit-tooling">official plugin page</a>
![https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1PBqxCHnbDKJwzj8IhJk%2Fuploads%2Fo2jPveKWzw47uGTxMm2Q%2Fimage.png?alt=media&token=403c70a1-fa12-435d-99c2-aab79493de97](img.png)
+7 -2
View File
@@ -1,12 +1,12 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.9.0"
kotlin("jvm") version "1.9.23"
id("io.codearte.nexus-staging") version "0.30.0"
}
group = "tech.carcadex"
version = "1.0.0.2"
version = project.findProperty("version").toString()
repositories {
mavenCentral()
@@ -34,4 +34,9 @@ tasks.register("publishAll") {
dependsOn(tasks.getByPath("serialization:publish"))
dependsOn(tasks.getByPath("genref:publish"))
dependsOn(tasks.getByPath("messages:publish"))
dependsOn(tasks.getByPath("commands:publish"))
dependsOn(tasks.getByPath("utility:publish"))
dependsOn(tasks.getByPath("exposed:publish"))
dependsOn(tasks.getByPath("thirdparty:publish"))
dependsOn(tasks.getByPath("menu:publish"))
}
@@ -0,0 +1,12 @@
package tech.carcadex.kotlinbukkitkit.commands
import tech.carcadex.kotlinbukkitkit.architecture.KotlinPlugin
import tech.carcadex.kotlinbukkitkit.commands.dsl.builder.simpleCommand
fun KotlinPlugin.start() {
simpleCommand("test", permission = "test.use") {
val arg1 = string(0)
val arg2 = integer(1)
sender.sendMessage(arg1)
}
}
+42
View File
@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+121
View File
@@ -0,0 +1,121 @@
plugins {
id("java")
kotlin("jvm")
kotlin("plugin.serialization") version "1.8.21"
id("com.github.johnrengelman.shadow") version "7.1.2"
id("maven-publish")
id("signing")
}
group = "tech.carcadex"
val globalArtifactId = "kotlinbukkitkit-exposed"
val globalName = "kotlinbukkitkit-exposed"
repositories {
mavenCentral()
maven {
name = "spigotmc-repo"
url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
}
}
dependencies {
implementation("org.jetbrains.exposed", "exposed-core", "0.45.0")
implementation("org.jetbrains.exposed", "exposed-dao", "0.45.0")
implementation("com.zaxxer", "HikariCP", "4.0.3")
compileOnly("org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT")
api(project(":architecture"))
api(project(":extensions"))
api(project(":utility"))
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(8)
}
kotlin {
sourceSets.main {
kotlin.srcDir("build/generated/ksp/main/kotlin")
}
sourceSets.test {
kotlin.srcDir("build/generated/ksp/test/kotlin")
}
}
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = globalArtifactId
groupId = "tech.carcadex"
version = version
from(components["java"])
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
pom {
artifactId = globalArtifactId
groupId = "tech.carcadex"
version = version
name.set(globalArtifactId)
description.set(globalArtifactId)
url.set("https://github.com/CarcadeX/KotlinBukkitKit")
licenses {
license {
name.set("MIT")
url.set("https://mit-license.org/")
}
}
developers {
developer {
id.set("itzRedTea")
name.set("Red Tea")
email.set("red__tea@outlook.com")
}
}
scm {
connection.set("scm:https://github.com/CarcadeX/$globalName.git")
developerConnection.set("git@github.com:CarcadeX/$globalName.git")
url.set("https://github.com/CarcadeX/KotlinBukkitKit")
}
}
}
}
repositories {
maven {
val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
credentials {
username = "itzRedTea"
password = project.findProperty("password").toString()
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications.getByName("maven"))
}
@@ -0,0 +1,64 @@
package tech.carcadex.kotlinbukkitkit.exposed
import kotlinx.serialization.Serializable
import java.io.File
import java.sql.SQLException
public interface DatabaseTypeConfigHeader {
public val type: String
public val hostname: String
public val port: Short
public val database: String
public val user: String
public val password: String
}
@Serializable
public data class DatabaseTypeConfig(
override var type: String = "H2",
override var hostname: String = "localhost",
override var port: Short = 3306,
override var database: String = "kbapi_database",
override var user: String = "root",
override var password: String = "12345",
) : DatabaseTypeConfigHeader
public fun databaseTypeFrom(dataFolder: File, config: DatabaseTypeConfig): DatabaseType {
val type = DatabaseType.byName(config.type)
if (type != null) {
return when (type) {
DatabaseType.H2::class -> DatabaseType.H2(
dataFolder,
config.database,
)
DatabaseType.SQLite::class -> DatabaseType.SQLite(
dataFolder,
config.database,
)
DatabaseType.MySQL::class -> DatabaseType.MySQL(
config.hostname,
config.port,
config.database,
config.user,
config.password,
)
DatabaseType.PostgreSQL::class -> DatabaseType.PostgreSQL(
config.hostname,
config.port,
config.database,
config.user,
config.password,
)
DatabaseType.SQLServer::class -> DatabaseType.SQLServer(
config.hostname,
config.port,
config.database,
config.user,
config.password,
)
else -> throw SQLException("SQL type not finded or supported.")
}
} else {
throw SQLException("SQL type not finded or supported.")
}
}
@@ -0,0 +1,232 @@
package tech.carcadex.kotlinbukkitkit.exposed
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.URL
import java.net.URLClassLoader
import java.sql.SQLException
import kotlin.reflect.KClass
private const val KEY_FILE = "{file}"
public sealed class DatabaseType(
public val name: String,
public val jdbc: String,
public val driverClass: String,
public val driverLink: String,
) {
public companion object {
public val fileDatabases: Map<String, KClass<out DatabaseType>> =
mapOf("h2" to H2::class, "sqlite" to SQLite::class)
public val remoteDatabases: Map<String, KClass<out DatabaseType>> =
mapOf("mysql" to MySQL::class, "postgresql" to PostgreSQL::class, "sqlserver" to SQLServer::class)
public val databases: Map<String, KClass<out DatabaseType>> = fileDatabases + remoteDatabases
public fun byName(name: String): KClass<out DatabaseType>? = databases[name.toLowerCase()]
}
public abstract fun dataSource(): HikariDataSource
public abstract fun config(): HikariConfig
public abstract class FileDatabaseType(
name: String,
jdbc: String,
driverClass: String,
driverLink: String,
public val dataFolder: File,
public val file: String,
public val databaseExtension: String,
public val needFileCreation: Boolean,
) : DatabaseType(name, jdbc, driverClass, driverLink) {
private val realFile = File(dataFolder, "$file.$databaseExtension")
override fun dataSource(): HikariDataSource {
if (needFileCreation && !realFile.exists()) realFile.createNewFile()
loadDependency()
return HikariDataSource(config())
}
override fun config(): HikariConfig = HikariConfig().apply {
driverClassName = driverClass
jdbcUrl = jdbc.replace(KEY_FILE, realFile.path)
}
}
public abstract class RemoteDatabaseType(
name: String,
jdbc: String,
driverClass: String,
driverLink: String,
public val hostname: String,
public val port: Short,
public val database: String,
public val username: String,
public val password: String,
) : DatabaseType(name, jdbc, driverClass, driverLink) {
override fun dataSource(): HikariDataSource {
loadDependency()
return HikariDataSource(config())
}
override fun config(): HikariConfig = HikariConfig().apply {
driverClassName = driverClass
jdbcUrl = jdbc
username = this@RemoteDatabaseType.username
password = this@RemoteDatabaseType.password
}
}
public class H2(
dataFolder: File,
file: String,
) : FileDatabaseType(
"H2",
"jdbc:h2:file:./$KEY_FILE",
"org.h2.Driver",
"https://repo1.maven.org/maven2/com/h2database/h2/1.4.199/h2-1.4.199.jar",
dataFolder,
file,
"h2.db",
false,
)
public class SQLite(
dataFolder: File,
file: String,
) : FileDatabaseType(
"SQLite",
"jdbc:sqlite:./$KEY_FILE",
"org.sqlite.JDBC",
"https://bitbucket.org/xerial/sqlite-jdbc/downloads/sqlite-jdbc-3.23.1.jar",
dataFolder,
file,
"sqlite.db",
true,
) {
// https://github.com/brettwooldridge/HikariCP/issues/393#issuecomment-135580191
override fun config(): HikariConfig {
return super.config().apply {
connectionTestQuery = "SELECT 1"
}
}
}
public class MySQL(
hostname: String,
port: Short,
database: String,
username: String,
password: String,
) : RemoteDatabaseType(
"MySQL",
"jdbc:mysql://$hostname:$port/$database",
"com.mysql.jdbc.Driver",
"https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.11/mysql-connector-java-8.0.11.jar",
hostname, port, database,
username, password,
)
public class PostgreSQL(
hostname: String,
port: Short,
database: String,
username: String,
password: String,
) : RemoteDatabaseType(
"PostgreSQL",
"jdbc:postgresql://$hostname:$port/$database",
"org.postgresql.Driver",
"https://jdbc.postgresql.org/download/postgresql-42.2.2.jre7.jar",
hostname, port, database,
username, password,
)
public class SQLServer(
hostname: String,
port: Short,
database: String,
username: String,
password: String,
) : RemoteDatabaseType(
"SQLServer",
"jdbc:sqlserver://$hostname:$port;databaseName=$database",
"com.microsoft.sqlserver.jdbc.SQLServerDriver",
"https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc/6.4.0.jre8/mssql-jdbc-6.4.0.jre8.jar",
hostname, port, database,
username, password,
)
private val libsFolder = File("klibs").apply { mkdirs() }
private val jarFile = File(libsFolder, "$name-Driver.jar")
protected fun loadDependency() {
try {
Class.forName(driverClass)
} catch (e: ClassNotFoundException) {
if (jarFile.exists()) {
loadDriver()
} else {
try {
downloadDriver()
} catch (e: Exception) {
throw SQLException("Cant download the driver dependencie of $name")
}
try {
loadDriver()
Class.forName(driverClass)
} catch (e: Exception) {
jarFile.delete()
throw SQLException("Cant load the driver dependencies of $name")
}
}
}
}
private fun loadDriver() {
val url = jarFile.toURI().toURL()
val classLoader = DatabaseType::class.java.classLoader as URLClassLoader
val method = URLClassLoader::class.java.getDeclaredMethod("addURL", URL::class.java)
method.isAccessible = true
method.invoke(classLoader, url)
}
private fun downloadDriver() {
val input = URL(driverLink).openStream()
if (jarFile.exists()) {
if (jarFile.isDirectory) {
throw IOException("File '" + jarFile.name + "' is a directory")
}
if (!jarFile.canWrite()) {
throw IOException("File '" + jarFile.name + "' cannot be written")
}
} else {
val parent = jarFile.parentFile
if (parent != null && !parent.exists() && !parent.mkdirs()) {
throw IOException("File '" + jarFile.name + "' could not be created")
}
}
val output = FileOutputStream(jarFile)
val buffer = ByteArray(4096)
var n = input.read(buffer)
while (-1 != n) {
output.write(buffer, 0, n)
n = input.read(buffer)
}
input.close()
output.close()
}
}
@@ -0,0 +1,162 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import org.bukkit.Bukkit
import org.bukkit.block.Block
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import kotlin.reflect.KProperty
public fun Entity<*>.block(column: Column<String>): ExposedDelegate<Block> = BlockExposedDelegate(column)
@JvmName("blockNullable")
public fun Entity<*>.block(column: Column<String?>): ExposedDelegate<Block?> = BlockExposedDelegateNullable(column)
public fun Entity<*>.block(
worldColumn: Column<String>,
xColumn: Column<Int>,
yColumn: Column<Int>,
zColumn: Column<Int>,
): ExposedDelegate<Block> = BlockMultiColumnExposedDelegate(worldColumn, xColumn, yColumn, zColumn)
@JvmName("blockNullable")
public fun Entity<*>.nullableBlock(
worldColumn: Column<String?>,
xColumn: Column<Int?>,
yColumn: Column<Int?>,
zColumn: Column<Int?>,
): ExposedDelegate<Block?> = BlockMultiColumnExposedDelegateNullable(worldColumn, xColumn, yColumn, zColumn)
public class BlockExposedDelegate(
public val column: Column<String>,
) : ExposedDelegate<Block> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Block {
val data = entity.run { column.getValue(this, desc) }
val slices = data.split(";")
val worldName = slices[0]
val world = requireNotNull(Bukkit.getWorld(worldName)) { "World '$worldName' retrieving from database unavailable." }
return world.getBlockAt(
slices[1].toInt(),
slices[2].toInt(),
slices[3].toInt(),
)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Block,
) {
val parsed = value.run { "${world.name};$x;$y;$z" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class BlockExposedDelegateNullable(
public val column: Column<String?>,
) : ExposedDelegate<Block?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Block? {
val data = entity.run { column.getValue(this, desc) }
val slices = data?.split(";")
return slices?.let {
val worldName = it[0]
val world = requireNotNull(Bukkit.getWorld(worldName)) { "World '$worldName' retrieving from database unavailable." }
world.getBlockAt(
it[1].toInt(),
it[2].toInt(),
it[3].toInt(),
)
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Block?,
) {
val parsed = value?.run { "${world.name};$x;$y;$z" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class BlockMultiColumnExposedDelegate(
public val worldColumn: Column<String>,
public val xColumn: Column<Int>,
public val yColumn: Column<Int>,
public val zColumn: Column<Int>,
) : ExposedDelegate<Block> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Block {
val worldName = entity.run { worldColumn.getValue(this, desc) }
val x = entity.run { xColumn.getValue(this, desc) }
val y = entity.run { yColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
val world = requireNotNull(Bukkit.getWorld(worldName)) { "World '$worldName' retrieving from database unavailable." }
return world.getBlockAt(x, y, z)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Block,
) {
entity.apply {
value.apply {
worldColumn.setValue(entity, desc, world.name)
xColumn.setValue(entity, desc, x)
yColumn.setValue(entity, desc, y)
zColumn.setValue(entity, desc, z)
}
}
}
}
public class BlockMultiColumnExposedDelegateNullable(
public val worldColumn: Column<String?>,
public val xColumn: Column<Int?>,
public val yColumn: Column<Int?>,
public val zColumn: Column<Int?>,
) : ExposedDelegate<Block?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Block? {
val worldName = entity.run { worldColumn.getValue(this, desc) }
val x = entity.run { xColumn.getValue(this, desc) }
val y = entity.run { yColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
return if (
worldName != null &&
x != null && y != null && z != null
) {
val world = requireNotNull(Bukkit.getWorld(worldName)) { "World '$worldName' retrieving from database unavailable." }
world.getBlockAt(x, y, z)
} else {
null
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Block?,
) {
entity.apply {
worldColumn.setValue(entity, desc, value?.world?.name)
xColumn.setValue(entity, desc, value?.x)
yColumn.setValue(entity, desc, value?.y)
zColumn.setValue(entity, desc, value?.z)
}
}
}
@@ -0,0 +1,148 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import tech.carcadex.kotlinbukkitkit.utility.types.BlockPos
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import kotlin.reflect.KProperty
public fun Entity<*>.blockPos(column: Column<String>): ExposedDelegate<BlockPos> = BlockPosExposedDelegate(column)
@JvmName("blockPosNullable")
public fun Entity<*>.blockPos(column: Column<String?>): ExposedDelegate<BlockPos?> = BlockPosExposedDelegateNullable(column)
public fun Entity<*>.blockPos(
xColumn: Column<Int>,
yColumn: Column<Int>,
zColumn: Column<Int>,
): ExposedDelegate<BlockPos> = BlockPosMultiColumnExposedDelegate(xColumn, yColumn, zColumn)
@JvmName("blockPosNullable")
public fun Entity<*>.nullableBlockPos(
xColumn: Column<Int?>,
yColumn: Column<Int?>,
zColumn: Column<Int?>,
): ExposedDelegate<BlockPos?> = BlockPosMultiColumnExposedDelegateNullable(xColumn, yColumn, zColumn)
public class BlockPosExposedDelegate(
public val column: Column<String>,
) : ExposedDelegate<BlockPos> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): BlockPos {
val data = entity.run { column.getValue(this, desc) }
val slices = data.split(";")
return BlockPos(
slices[0].toInt(),
slices[1].toInt(),
slices[2].toInt(),
)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: BlockPos,
) {
val parsed = value.run { "$x;$y;$z" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class BlockPosExposedDelegateNullable(
public val column: Column<String?>,
) : ExposedDelegate<BlockPos?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): BlockPos? {
val data = entity.run { column.getValue(this, desc) }
val slices = data?.split(";")
return slices?.let {
BlockPos(
it[0].toInt(),
it[1].toInt(),
it[2].toInt(),
)
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: BlockPos?,
) {
val parsed = value?.run { "$x;$y;$z" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class BlockPosMultiColumnExposedDelegate(
public val xColumn: Column<Int>,
public val yColumn: Column<Int>,
public val zColumn: Column<Int>,
) : ExposedDelegate<BlockPos> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): BlockPos {
val x = entity.run { xColumn.getValue(this, desc) }
val y = entity.run { yColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
return BlockPos(x, y, z)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: BlockPos,
) {
entity.apply {
value.apply {
xColumn.setValue(entity, desc, x)
yColumn.setValue(entity, desc, y)
zColumn.setValue(entity, desc, z)
}
}
}
}
public class BlockPosMultiColumnExposedDelegateNullable(
public val xColumn: Column<Int?>,
public val yColumn: Column<Int?>,
public val zColumn: Column<Int?>,
) : ExposedDelegate<BlockPos?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): BlockPos? {
val x = entity.run { xColumn.getValue(this, desc) }
val y = entity.run { yColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
return if (
x != null && y != null && z != null
) {
BlockPos(
x,
y,
z,
)
} else {
null
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: BlockPos?,
) {
entity.apply {
xColumn.setValue(entity, desc, value?.x)
yColumn.setValue(entity, desc, value?.y)
zColumn.setValue(entity, desc, value?.z)
}
}
}
@@ -0,0 +1,35 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import org.jetbrains.exposed.dao.Entity
import kotlin.reflect.KProperty
public fun <T> ExposedDelegate<T>.cached(): ExposedDelegate<T> = CachedExposedDelegate(this)
public class CachedExposedDelegate<T>(
private val delegate: ExposedDelegate<T>,
) : ExposedDelegate<T> {
private var cache: T? = null
private var isCached: Boolean = false
override fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): T {
if (!isCached) {
cache = delegate.getValue(entity, desc)
isCached = true
}
return cache!!
}
override fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: T,
) {
delegate.setValue(entity, desc, value)
cache = value
isCached = true
}
}
@@ -0,0 +1,152 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import org.bukkit.Bukkit
import org.bukkit.Chunk
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import kotlin.reflect.KProperty
public fun Entity<*>.chunk(column: Column<String>): ExposedDelegate<Chunk> = ChunkExposedDelegate(column)
@JvmName("chunkNullable")
public fun Entity<*>.chunk(column: Column<String?>): ExposedDelegate<Chunk?> = ChunkExposedDelegateNullable(column)
public fun Entity<*>.chunk(
worldColumn: Column<String>,
xColumn: Column<Int>,
zColumn: Column<Int>,
): ExposedDelegate<Chunk> = ChunkMultiColumnExposedDelegate(worldColumn, xColumn, zColumn)
@JvmName("chunkNullable")
public fun Entity<*>.chunk(
worldColumn: Column<String?>,
xColumn: Column<Int?>,
zColumn: Column<Int?>,
): ExposedDelegate<Chunk?> = ChunkMultiColumnExposedDelegateNullable(worldColumn, xColumn, zColumn)
public class ChunkExposedDelegate(
public val column: Column<String>,
) : ExposedDelegate<Chunk> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Chunk {
val data = entity.run { column.getValue(this, desc) }
val slices = data.split(";")
val worldName = slices[0]
val world = requireNotNull(Bukkit.getWorld(worldName)) { "World '$worldName' retrieving from database unavailable." }
return world.getChunkAt(
slices[1].toInt(),
slices[2].toInt(),
)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Chunk,
) {
val parsed = value.run { "${world.name};$x;$z" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class ChunkExposedDelegateNullable(
public val column: Column<String?>,
) : ExposedDelegate<Chunk?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Chunk? {
val data = entity.run { column.getValue(this, desc) }
val slices = data?.split(";")
return slices?.let {
val worldName = it[0]
val world = requireNotNull(Bukkit.getWorld(worldName)) { "World '$worldName' retrieving from database unavailable." }
world.getChunkAt(
it[1].toInt(),
it[2].toInt(),
)
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Chunk?,
) {
val parsed = value?.run { "${world.name};$x;$z" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class ChunkMultiColumnExposedDelegate(
public val worldColumn: Column<String>,
public val xColumn: Column<Int>,
public val zColumn: Column<Int>,
) : ExposedDelegate<Chunk> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Chunk {
val worldName = entity.run { worldColumn.getValue(this, desc) }
val x = entity.run { xColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
val world = requireNotNull(Bukkit.getWorld(worldName)) { "World '$worldName' retrieving from database unavailable." }
return world.getChunkAt(x, z)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Chunk,
) {
entity.apply {
value.apply {
worldColumn.setValue(entity, desc, world.name)
xColumn.setValue(entity, desc, x)
zColumn.setValue(entity, desc, z)
}
}
}
}
public class ChunkMultiColumnExposedDelegateNullable(
public val worldColumn: Column<String?>,
public val xColumn: Column<Int?>,
public val zColumn: Column<Int?>,
) : ExposedDelegate<Chunk?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Chunk? {
val worldName = entity.run { worldColumn.getValue(this, desc) }
val x = entity.run { xColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
return if (
worldName != null &&
x != null && z != null
) {
val world = requireNotNull(Bukkit.getWorld(worldName)) { "World '$worldName' retrieving from database unavailable." }
world.getChunkAt(x, z)
} else {
null
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Chunk?,
) {
entity.apply {
worldColumn.setValue(entity, desc, value?.world?.name)
xColumn.setValue(entity, desc, value?.x)
zColumn.setValue(entity, desc, value?.z)
}
}
}
@@ -0,0 +1,137 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import tech.carcadex.kotlinbukkitkit.utility.types.ChunkPos
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import kotlin.reflect.KProperty
public fun Entity<*>.chunkPos(column: Column<String>): ExposedDelegate<ChunkPos> = ChunkPosExposedDelegate(column)
@JvmName("chunkPosNullable")
public fun Entity<*>.chunkPos(column: Column<String?>): ExposedDelegate<ChunkPos?> = ChunkPosExposedDelegateNullable(column)
public fun Entity<*>.chunkPos(
xColumn: Column<Int>,
zColumn: Column<Int>,
): ExposedDelegate<ChunkPos> = ChunkPosMultiColumnExposedDelegate(xColumn, zColumn)
@JvmName("chunkPosNullable")
public fun Entity<*>.chunkPos(
xColumn: Column<Int?>,
zColumn: Column<Int?>,
): ExposedDelegate<ChunkPos?> = ChunkPosMultiColumnExposedDelegateNullable(xColumn, zColumn)
public class ChunkPosExposedDelegate(
public val column: Column<String>,
) : ExposedDelegate<ChunkPos> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): ChunkPos {
val data = entity.run { column.getValue(this, desc) }
val slices = data.split(";")
return ChunkPos(
slices[0].toInt(),
slices[1].toInt(),
)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: ChunkPos,
) {
val parsed = value.run { "$x;$z" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class ChunkPosExposedDelegateNullable(
public val column: Column<String?>,
) : ExposedDelegate<ChunkPos?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): ChunkPos? {
val data = entity.run { column.getValue(this, desc) }
val slices = data?.split(";")
return slices?.let {
ChunkPos(
it[0].toInt(),
it[1].toInt(),
)
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: ChunkPos?,
) {
val parsed = value?.run { "$x;$z" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class ChunkPosMultiColumnExposedDelegate(
public val xColumn: Column<Int>,
public val zColumn: Column<Int>,
) : ExposedDelegate<ChunkPos> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): ChunkPos {
val x = entity.run { xColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
return ChunkPos(x, z)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: ChunkPos,
) {
entity.apply {
value.apply {
xColumn.setValue(entity, desc, x)
zColumn.setValue(entity, desc, z)
}
}
}
}
public class ChunkPosMultiColumnExposedDelegateNullable(
public val xColumn: Column<Int?>,
public val zColumn: Column<Int?>,
) : ExposedDelegate<ChunkPos?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): ChunkPos? {
val x = entity.run { xColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
return if (
x != null && z != null
) {
ChunkPos(
x,
z,
)
} else {
null
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: ChunkPos?,
) {
entity.apply {
xColumn.setValue(entity, desc, value?.x)
zColumn.setValue(entity, desc, value?.z)
}
}
}
@@ -0,0 +1,18 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import org.jetbrains.exposed.dao.Entity
import kotlin.reflect.KProperty
public interface ExposedDelegate<T> {
public operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): T
public operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: T,
)
}
@@ -0,0 +1,82 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import org.bukkit.inventory.ItemStack
import org.bukkit.util.io.BukkitObjectInputStream
import org.bukkit.util.io.BukkitObjectOutputStream
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.statements.api.ExposedBlob
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import kotlin.reflect.KProperty
/**
* NOTE: This not save NBT and could break version to version, if you pretend
* to upgrade your server and this keep the same, I can't guarantee that.
*/
public fun Entity<*>.itemStack(column: Column<ExposedBlob>): ExposedDelegate<ItemStack> = ItemStackExposedDelegate(column)
@JvmName("itemStackNullable")
public fun Entity<*>.itemStack(column: Column<ExposedBlob?>): ExposedDelegate<ItemStack?> = ItemStackExposedDelegateNullable(column)
public class ItemStackExposedDelegate(
public val column: Column<ExposedBlob>,
) : ExposedDelegate<ItemStack> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): ItemStack {
val blob = entity.run { column.getValue(this, desc) }
return toItemStack(blob.bytes)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: ItemStack,
) {
val byteArray = toByteArray(value)
val blob = ExposedBlob(byteArray)
entity.apply { column.setValue(this, desc, blob) }
}
}
public class ItemStackExposedDelegateNullable(
public val column: Column<ExposedBlob?>,
) : ExposedDelegate<ItemStack?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): ItemStack? {
val blob = entity.run { column.getValue(this, desc) }
return blob?.bytes?.let { toItemStack(it) }
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: ItemStack?,
) {
val byteArray = value?.let { toByteArray(it) }
val blob = byteArray?.let { ExposedBlob(it) }
entity.apply { column.setValue(this, desc, blob) }
}
}
private fun toByteArray(item: ItemStack): ByteArray {
val outputStream = ByteArrayOutputStream()
BukkitObjectOutputStream(outputStream).use {
it.writeObject(item)
}
return outputStream.toByteArray()
}
private fun toItemStack(byteArray: ByteArray): ItemStack {
return ByteArrayInputStream(byteArray).use {
BukkitObjectInputStream(it).use {
it.readObject() as ItemStack
}
}
}
@@ -0,0 +1,190 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import tech.carcadex.kotlinbukkitkit.extensions.world
import org.bukkit.Bukkit
import org.bukkit.Location
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import kotlin.reflect.KProperty
public fun Entity<*>.location(column: Column<String>): ExposedDelegate<Location> = LocationExposedDelegate(column)
@JvmName("locationNullable")
public fun Entity<*>.location(column: Column<String?>): ExposedDelegate<Location?> = LocationExposedDelegateNullable(column)
public fun Entity<*>.location(
worldColumn: Column<String>,
xColumn: Column<Double>,
yColumn: Column<Double>,
zColumn: Column<Double>,
yawColumn: Column<Float>,
pitchColumn: Column<Float>,
): ExposedDelegate<Location> = LocationMultiColumnExposedDelegate(worldColumn, xColumn, yColumn, zColumn, yawColumn, pitchColumn)
@JvmName("locationNullable")
public fun Entity<*>.location(
worldColumn: Column<String?>,
xColumn: Column<Double?>,
yColumn: Column<Double?>,
zColumn: Column<Double?>,
yawColumn: Column<Float?>,
pitchColumn: Column<Float?>,
): ExposedDelegate<Location?> = LocationMultiColumnExposedDelegateNullable(worldColumn, xColumn, yColumn, zColumn, yawColumn, pitchColumn)
public class LocationExposedDelegate(
public val column: Column<String>,
) : ExposedDelegate<Location> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Location {
val data = entity.run { column.getValue(this, desc) }
val slices = data.split(";")
return Location(
Bukkit.getWorld(slices[0]),
slices[1].toDouble(),
slices[2].toDouble(),
slices[3].toDouble(),
slices[4].toFloat(),
slices[5].toFloat(),
)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Location,
) {
val world = requireNotNull(value.world) { "Location with nullable world can't be stored in database" }
val parsed = value.run { "${world.name};$x;$y;$z;$yaw;$pitch" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class LocationExposedDelegateNullable(
public val column: Column<String?>,
) : ExposedDelegate<Location?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Location? {
val data = entity.run { column.getValue(this, desc) }
val slices = data?.split(";")
return slices?.let {
Location(
Bukkit.getWorld(it[0]),
it[1].toDouble(),
it[2].toDouble(),
it[3].toDouble(),
it[4].toFloat(),
it[5].toFloat(),
)
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Location?,
) {
val parsed = value?.run {
val world = requireNotNull(world) { "Location with nullable world can't be stored in database" }
"${world.name};$x;$y;$z;$yaw;$pitch"
}
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class LocationMultiColumnExposedDelegate(
public val worldColumn: Column<String>,
public val xColumn: Column<Double>,
public val yColumn: Column<Double>,
public val zColumn: Column<Double>,
public val yawColumn: Column<Float>,
public val pitchColumn: Column<Float>,
) : ExposedDelegate<Location> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Location {
val worldName = entity.run { worldColumn.getValue(this, desc) }
val x = entity.run { xColumn.getValue(this, desc) }
val y = entity.run { yColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
val yaw = entity.run { yawColumn.getValue(this, desc) }
val pitch = entity.run { pitchColumn.getValue(this, desc) }
return Location(
Bukkit.getWorld(worldName),
x,
y,
z,
yaw,
pitch,
)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Location,
) {
entity.apply {
value.apply {
val world = requireNotNull(world) { "Location with nullable world can't be stored in database" }
worldColumn.setValue(entity, desc, world.name)
xColumn.setValue(entity, desc, x)
yColumn.setValue(entity, desc, y)
zColumn.setValue(entity, desc, z)
yawColumn.setValue(entity, desc, yaw)
pitchColumn.setValue(entity, desc, pitch)
}
}
}
}
public class LocationMultiColumnExposedDelegateNullable(
public val worldColumn: Column<String?>,
public val xColumn: Column<Double?>,
public val yColumn: Column<Double?>,
public val zColumn: Column<Double?>,
public val yawColumn: Column<Float?>,
public val pitchColumn: Column<Float?>,
) : ExposedDelegate<Location?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): Location? {
val worldName = entity.run { worldColumn.getValue(this, desc) }
val x = entity.run { xColumn.getValue(this, desc) }
val y = entity.run { yColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
val yaw = entity.run { yawColumn.getValue(this, desc) }
val pitch = entity.run { pitchColumn.getValue(this, desc) }
return if (
worldName != null &&
x != null && y != null && z != null &&
yaw != null && pitch != null
) {
Location(Bukkit.getWorld(worldName), x, y, z, yaw, pitch)
} else {
null
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: Location?,
) {
entity.apply {
worldColumn.setValue(entity, desc, value?.world?.name)
xColumn.setValue(entity, desc, value?.x)
yColumn.setValue(entity, desc, value?.y)
zColumn.setValue(entity, desc, value?.z)
yawColumn.setValue(entity, desc, value?.yaw)
pitchColumn.setValue(entity, desc, value?.pitch)
}
}
}
@@ -0,0 +1,177 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import tech.carcadex.kotlinbukkitkit.utility.types.LocationPos
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import kotlin.reflect.KProperty
public fun Entity<*>.locationPos(column: Column<String>): ExposedDelegate<LocationPos> = LocationPosExposedDelegate(column)
@JvmName("locationPosNullable")
public fun Entity<*>.locationPos(column: Column<String?>): ExposedDelegate<LocationPos?> = LocationPosExposedDelegateNullable(column)
public fun Entity<*>.locationPos(
xColumn: Column<Double>,
yColumn: Column<Double>,
zColumn: Column<Double>,
yawColumn: Column<Float>,
pitchColumn: Column<Float>,
): ExposedDelegate<LocationPos> = LocationPosMultiColumnExposedDelegate(xColumn, yColumn, zColumn, yawColumn, pitchColumn)
@JvmName("locationPosNullable")
public fun Entity<*>.locationPos(
xColumn: Column<Double?>,
yColumn: Column<Double?>,
zColumn: Column<Double?>,
yawColumn: Column<Float?>,
pitchColumn: Column<Float?>,
): ExposedDelegate<LocationPos?> = LocationPosMultiColumnExposedDelegateNullable(xColumn, yColumn, zColumn, yawColumn, pitchColumn)
public class LocationPosExposedDelegate(
public val column: Column<String>,
) : ExposedDelegate<LocationPos> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): LocationPos {
val data = entity.run { column.getValue(this, desc) }
val slices = data.split(";")
return LocationPos(
slices[0].toDouble(),
slices[1].toDouble(),
slices[2].toDouble(),
slices[3].toFloat(),
slices[4].toFloat(),
)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: LocationPos,
) {
val parsed = value.run { "$x;$y;$z;$yaw;$pitch" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class LocationPosExposedDelegateNullable(
public val column: Column<String?>,
) : ExposedDelegate<LocationPos?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): LocationPos? {
val data = entity.run { column.getValue(this, desc) }
val slices = data?.split(";")
return slices?.let {
LocationPos(
it[0].toDouble(),
it[1].toDouble(),
it[2].toDouble(),
it[3].toFloat(),
it[4].toFloat(),
)
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: LocationPos?,
) {
val parsed = value?.run { "$x;$y;$z;$yaw;$pitch" }
entity.apply { column.setValue(this, desc, parsed) }
}
}
public class LocationPosMultiColumnExposedDelegate(
public val xColumn: Column<Double>,
public val yColumn: Column<Double>,
public val zColumn: Column<Double>,
public val yawColumn: Column<Float>,
public val pitchColumn: Column<Float>,
) : ExposedDelegate<LocationPos> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): LocationPos {
val x = entity.run { xColumn.getValue(this, desc) }
val y = entity.run { yColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
val yaw = entity.run { yawColumn.getValue(this, desc) }
val pitch = entity.run { pitchColumn.getValue(this, desc) }
return LocationPos(
x,
y,
z,
yaw,
pitch,
)
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: LocationPos,
) {
entity.apply {
value.apply {
xColumn.setValue(entity, desc, x)
yColumn.setValue(entity, desc, y)
zColumn.setValue(entity, desc, z)
yawColumn.setValue(entity, desc, yaw)
pitchColumn.setValue(entity, desc, pitch)
}
}
}
}
public class LocationPosMultiColumnExposedDelegateNullable(
public val xColumn: Column<Double?>,
public val yColumn: Column<Double?>,
public val zColumn: Column<Double?>,
public val yawColumn: Column<Float?>,
public val pitchColumn: Column<Float?>,
) : ExposedDelegate<LocationPos?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): LocationPos? {
val x = entity.run { xColumn.getValue(this, desc) }
val y = entity.run { yColumn.getValue(this, desc) }
val z = entity.run { zColumn.getValue(this, desc) }
val yaw = entity.run { yawColumn.getValue(this, desc) }
val pitch = entity.run { pitchColumn.getValue(this, desc) }
return if (
x != null && y != null && z != null &&
yaw != null && pitch != null
) {
LocationPos(
x,
y,
z,
yaw,
pitch,
)
} else {
null
}
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: LocationPos?,
) {
entity.apply {
xColumn.setValue(entity, desc, value?.x)
yColumn.setValue(entity, desc, value?.y)
zColumn.setValue(entity, desc, value?.z)
yawColumn.setValue(entity, desc, value?.yaw)
pitchColumn.setValue(entity, desc, value?.pitch)
}
}
}
@@ -0,0 +1,47 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import tech.carcadex.kotlinbukkitkit.extensions.offlinePlayer
import org.bukkit.OfflinePlayer
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import java.util.*
import kotlin.reflect.KProperty
public fun Entity<*>.offlinePlayer(column: Column<UUID>): ExposedDelegate<OfflinePlayer> = OfflinePlayerExposedDelegate(column)
@JvmName("offlinePlayerNullable")
public fun Entity<*>.offlinePlayer(column: Column<UUID?>): ExposedDelegate<OfflinePlayer?> = OfflinePlayerExposedDelegateNullable(column)
public class OfflinePlayerExposedDelegate(
public val column: Column<UUID>,
) : ExposedDelegate<OfflinePlayer> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): OfflinePlayer {
val uuid = entity.run { column.getValue(this, desc) }
return offlinePlayer(uuid)
}
override operator fun <ID : Comparable<ID>> setValue(entity: Entity<ID>, desc: KProperty<*>, value: OfflinePlayer) {
entity.apply { column.setValue(this, desc, value.uniqueId) }
}
}
public class OfflinePlayerExposedDelegateNullable(
public val column: Column<UUID?>,
) : ExposedDelegate<OfflinePlayer?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): OfflinePlayer? {
val uuid = entity.run { column.getValue(this, desc) }
return uuid?.let { offlinePlayer(it) }
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: OfflinePlayer?,
) {
entity.apply { column.setValue(this, desc, value?.uniqueId) }
}
}
@@ -0,0 +1,52 @@
package tech.carcadex.kotlinbukkitkit.exposed.delegate
import org.bukkit.Bukkit
import org.bukkit.World
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Column
import kotlin.reflect.KProperty
public fun Entity<*>.world(column: Column<String>): ExposedDelegate<World> = WorldExposedDelegate(column)
@JvmName("worldNullable")
public fun Entity<*>.world(column: Column<String?>): ExposedDelegate<World?> = WorldExposedDelegateNullable(column)
public class WorldExposedDelegate(
public val column: Column<String>,
) : ExposedDelegate<World> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): World {
val data = entity.run { column.getValue(this, desc) }
return requireNotNull(Bukkit.getWorld(data)) { "World '$data' retrieving from database not loaded at server." }
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: World,
) {
entity.apply { column.setValue(this, desc, value.name) }
}
}
public class WorldExposedDelegateNullable(
public val column: Column<String?>,
) : ExposedDelegate<World?> {
override operator fun <ID : Comparable<ID>> getValue(
entity: Entity<ID>,
desc: KProperty<*>,
): World? {
val data = entity.run { column.getValue(this, desc) }
return data?.let { Bukkit.getWorld(it) }
}
override operator fun <ID : Comparable<ID>> setValue(
entity: Entity<ID>,
desc: KProperty<*>,
value: World?,
) {
entity.apply { column.setValue(this, desc, value?.name) }
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=1.0.0.2
version=1.0.0.3
signing.gnupg.executable=C:/Program Files (x86)/GnuPG/bin/gpg.exe
signing.gnupg.useLegacyGpg=false
+42
View File
@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+117
View File
@@ -0,0 +1,117 @@
plugins {
id("java")
kotlin("jvm")
kotlin("plugin.serialization") version "1.8.21"
id("com.github.johnrengelman.shadow") version "7.1.2"
id("maven-publish")
id("signing")
}
group = "tech.carcadex"
val globalArtifactId = "kotlinbukkitkit-menu"
val globalName = "kotlinbukkitkit-menu"
repositories {
mavenCentral()
maven {
name = "spigotmc-repo"
url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
}
}
dependencies {
compileOnly("org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT")
api(project(":architecture"))
api(project(":extensions"))
api(project(":utility"))
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(8)
}
kotlin {
sourceSets.main {
kotlin.srcDir("build/generated/ksp/main/kotlin")
}
sourceSets.test {
kotlin.srcDir("build/generated/ksp/test/kotlin")
}
}
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = globalArtifactId
groupId = "tech.carcadex"
version = version
from(components["java"])
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
pom {
artifactId = globalArtifactId
groupId = "tech.carcadex"
version = version
name.set(globalArtifactId)
description.set(globalArtifactId)
url.set("https://github.com/CarcadeX/KotlinBukkitKit")
licenses {
license {
name.set("MIT")
url.set("https://mit-license.org/")
}
}
developers {
developer {
id.set("itzRedTea")
name.set("Red Tea")
email.set("red__tea@outlook.com")
}
}
scm {
connection.set("scm:https://github.com/CarcadeX/$globalName.git")
developerConnection.set("git@github.com:CarcadeX/$globalName.git")
url.set("https://github.com/CarcadeX/KotlinBukkitKit")
}
}
}
}
repositories {
maven {
val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
credentials {
username = "itzRedTea"
password = project.findProperty("password").toString()
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications.getByName("maven"))
}
@@ -0,0 +1,51 @@
package tech.carcadex.kotlinbukkitkit.menu
import tech.carcadex.kotlinbukkitkit.architecture.extensions.WithPlugin
import tech.carcadex.kotlinbukkitkit.menu.slot.Slot
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.InventoryHolder
import org.bukkit.plugin.Plugin
import java.util.*
public interface Menu<T : Slot> : WithPlugin<Plugin>, InventoryHolder {
public var title: String
public var lines: Int
public var cancelOnClick: Boolean
public val viewers: Map<Player, Inventory>
public val slots: TreeMap<Int, T>
public val data: WeakHashMap<String, Any>
public val playerData: WeakHashMap<Player, WeakHashMap<String, Any>>
public val eventHandler: MenuEventHandler
public var baseSlot: T
public var updateDelay: Long
public fun setSlot(slot: Int, slotObj: T)
public fun update(players: Set<Player> = viewers.keys)
public fun updateSlot(slot: Slot, players: Set<Player> = viewers.keys)
public fun update(vararg players: Player): Unit = update(players.toSet())
public fun updateSlot(slot: Slot, vararg players: Player): Unit = updateSlot(slot, players.toSet())
public fun openToPlayer(vararg players: Player)
public fun clearData() {
data.clear()
for (slot in slotsWithBaseSlot())
slot.clearSlotData()
}
public fun clearPlayerData(player: Player) {
playerData.remove(player)
for (slot in slotsWithBaseSlot())
slot.clearPlayerData(player)
}
public fun close(player: Player, closeInventory: Boolean = true)
}
@@ -0,0 +1,105 @@
package tech.carcadex.kotlinbukkitkit.menu
import tech.carcadex.kotlinbukkitkit.architecture.PluginDisableAware
import tech.carcadex.kotlinbukkitkit.architecture.PluginDisableAwareController
import tech.carcadex.kotlinbukkitkit.extensions.KListener
import tech.carcadex.kotlinbukkitkit.extensions.onlinePlayers
import tech.carcadex.kotlinbukkitkit.extensions.registerEvents
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotInteract
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.inventory.InventoryDragEvent
import org.bukkit.event.inventory.InventoryType
import org.bukkit.event.player.PlayerPickupItemEvent
import org.bukkit.plugin.Plugin
internal object MenuControllerPlugins : PluginDisableAwareController<MenuController>() {
override val factory: (plugin: Plugin) -> MenuController = { MenuController(it) }
}
internal class MenuController(
override val plugin: Plugin,
) : KListener<Plugin>, PluginDisableAware {
init {
registerEvents(plugin)
}
override fun onDisable() {
onlinePlayers.forEach {
val menu = getMenuFromPlayer(it)
?.takeIf { it.plugin.name == plugin.name }
menu?.close(it, true)
}
}
@EventHandler(ignoreCancelled = true)
fun clickInventoryEvent(event: InventoryClickEvent) {
if (event.view.type == InventoryType.CHEST) {
val player = event.whoClicked as Player
val inv = event.inventory
val menu = getMenuFromInventory(event.inventory)?.takeIfHasPlayer(player)
if (menu != null) {
if (menu.plugin.name != plugin.name) return
if (event.rawSlot == event.slot) {
val clickedSlot = event.slot + 1
val slot = menu.slotOrBaseSlot(clickedSlot)
val interact = MenuPlayerSlotInteract(
menu, clickedSlot, slot,
player, inv, slot.cancel,
event.click, event.action,
event.currentItem, event.cursor,
event.hotbarButton,
)
slot.eventHandler.interact(interact)
if (interact.canceled) event.isCancelled = true
} else {
// TODO move item
event.isCancelled = true
}
}
}
}
@EventHandler(ignoreCancelled = true)
fun clickInteractEvent(event: InventoryDragEvent) {
if (event.view.type == InventoryType.CHEST) {
val player = event.whoClicked as Player
val menu = getMenuFromInventory(event.inventory)?.takeIfHasPlayer(player)
if (menu != null) {
if (menu.plugin.name != plugin.name) return
val pass = event.inventorySlots.firstOrNull { it in event.rawSlots }
if (pass != null) event.isCancelled = true
}
}
}
@EventHandler
fun onClose(event: InventoryCloseEvent) {
if (event.view.type == InventoryType.CHEST) {
val player = event.player as Player
val menu = getMenuFromPlayer(player)
if (menu != null) {
if (menu.plugin.name != plugin.name) return
menu.close(player, false)
}
}
}
@EventHandler(ignoreCancelled = true)
fun onPickupItemEvent(event: PlayerPickupItemEvent) {
val menu = getMenuFromPlayer(event.player)
if (menu != null) {
if (menu.plugin.name != plugin.name) return
event.isCancelled = true
}
}
}
@@ -0,0 +1,14 @@
package tech.carcadex.kotlinbukkitkit.menu
public interface MenuEventHandler {
public fun update(update: MenuPlayerUpdate)
public fun close(close: MenuPlayerClose)
public fun moveToMenu(moveToMenu: MenuPlayerMoveTo)
public fun preOpen(preOpen: MenuPlayerPreOpen)
public fun open(open: MenuPlayerOpen)
}
@@ -0,0 +1,121 @@
package tech.carcadex.kotlinbukkitkit.menu
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
import java.util.*
public interface MenuPlayer {
public val menu: Menu<*>
public val player: Player
public fun putPlayerData(key: String, value: Any) {
val map = menu.playerData[player]
if (map != null) {
map[key] = value
} else menu.playerData[player] = WeakHashMap<String, Any>().apply { put(key, value) }
}
public fun getPlayerData(key: String): Any? = menu.playerData.get(player)?.get(key)
}
public interface MenuPlayerInventory : MenuPlayer {
public val inventory: Inventory
public fun close(): Unit = player.closeInventory()
public fun getItem(line: Int, slot: Int): ItemStack? {
return getItem(calculateSlot(line, slot))
}
public fun getItem(slot: Int): ItemStack? = inventory.getItem(slot - 1)
public fun setItem(line: Int, slot: Int, item: ItemStack?) {
setItem(calculateSlot(line, slot), item)
}
public fun setItem(slot: Int, item: ItemStack?) {
inventory.setItem(rawSlot(slot), item)
}
public fun getPlayerItem(line: Int, slot: Int): ItemStack? {
return getPlayerItem(calculateSlot(line, slot))
}
public fun getPlayerItem(slot: Int): ItemStack? = player.inventory.getItem(rawSlot(slot))
public fun setPlayerItem(line: Int, slot: Int, item: ItemStack?) {
setPlayerItem(calculateSlot(line, slot), item)
}
public fun setPlayerItem(slot: Int, item: ItemStack?) {
player.inventory.setItem(rawSlot(slot), item)
}
private fun getLineRange(line: Int) = calculateSlot(line, 1)..calculateEndLine(line)
public fun getLine(line: Int): List<ItemStack?> = getLineRange(line).map { getItem(it) }
public fun getPlayerLine(line: Int): List<ItemStack?> = getLineRange(line).map { getPlayerItem(it) }
public fun updateToPlayer() {
menu.update(player)
}
}
public interface MenuPlayerCancellable : MenuPlayer {
public var canceled: Boolean
}
public open class MenuPlayerInteract(
override val menu: Menu<*>,
override val player: Player,
override val inventory: Inventory,
override var canceled: Boolean,
) : MenuPlayerInventory, MenuPlayerCancellable
public class MenuPlayerPreOpen(
override val menu: Menu<*>,
override val player: Player,
override var canceled: Boolean = false,
) : MenuPlayerCancellable
public class MenuPlayerOpen(
override val menu: Menu<*>,
override val player: Player,
override val inventory: Inventory,
) : MenuPlayerInventory
public class MenuPlayerUpdate(
override val menu: Menu<*>,
override val player: Player,
override val inventory: Inventory,
public var title: String,
) : MenuPlayerInventory
public class MenuPlayerClose(
override val menu: Menu<*>,
override val player: Player,
) : MenuPlayer
public interface MenuPlayerMove : MenuPlayerInventory, MenuPlayerCancellable {
public val toMoveSlot: Int
public val toMoveItem: ItemStack?
}
public class MenuPlayerMoveTo(
menu: Menu<*>,
player: Player,
inventory: Inventory,
canceled: Boolean,
override val toMoveSlot: Int,
override val toMoveItem: ItemStack?,
public var targetSlot: Int,
) : MenuPlayerInteract(menu, player, inventory, canceled), MenuPlayerMove {
public var targetCurrentItem: ItemStack?
set(value) {
setItem(targetSlot, value)
}
get() = getItem(targetSlot)
}
@@ -0,0 +1,29 @@
package tech.carcadex.kotlinbukkitkit.menu
import tech.carcadex.kotlinbukkitkit.menu.slot.Slot
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
public fun calculateSlot(line: Int, slot: Int): Int = calculateStartLine(line) + slot
public fun calculateStartLine(line: Int): Int = calculateEndLine(line) - 9
public fun calculateEndLine(line: Int): Int = line * 9
public fun rawSlot(slot: Int): Int = slot - 1
public fun Menu<*>.slotOrBaseSlot(slot: Int): Slot = slots[slot] ?: baseSlot
public fun Menu<*>.rangeOfSlots(): IntRange = 1..calculateEndLine(lines)
public fun Menu<*>.viewersFromPlayers(players: Set<Player>): Map<Player, Inventory> = viewers.filterKeys { it in players }
public fun Menu<*>.slotsWithBaseSlot(): List<Slot> = slots.values + baseSlot
public fun Menu<*>.hasPlayer(player: Player): Boolean = viewers.containsKey(player)
public fun Menu<*>.takeIfHasPlayer(player: Player): Menu<*>? = if (hasPlayer(player)) this else null
public fun inventoryIsMenu(inventory: Inventory): Boolean = inventory.holder is Menu<*>
public fun getMenuFromInventory(inventory: Inventory): Menu<*>? = inventory.holder as? Menu<*>
public fun getMenuFromPlayer(player: Player): Menu<*>? {
return getMenuFromInventory(player.openInventory.topInventory)?.takeIfHasPlayer(player)
}
@@ -0,0 +1,8 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl
import org.bukkit.entity.Player
import java.util.*
public fun MenuDSL.putPlayerData(player: Player, key: String, value: Any): Any? = playerData.getOrPut(player, { WeakHashMap() }).put(key, value)
public fun MenuDSL.getPlayerData(player: Player, key: String): Any? = playerData.get(player)?.get(key)
@@ -0,0 +1,46 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl
import tech.carcadex.kotlinbukkitkit.menu.Menu
import tech.carcadex.kotlinbukkitkit.menu.calculateSlot
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot.SlotDSL
import org.bukkit.inventory.ItemStack
public inline fun MenuDSL.slot(
line: Int,
slot: Int,
item: ItemStack?,
block: SlotDSL.() -> Unit = {},
): SlotDSL = slot(calculateSlot(line, slot), item, block)
public inline fun MenuDSL.slot(
slot: Int,
item: ItemStack?,
block: SlotDSL.() -> Unit = {},
): SlotDSL = baseSlot.clone(item).apply(block).also {
setSlot(slot, it)
}
public interface MenuDSL : Menu<SlotDSL> {
override val eventHandler: MenuEventHandlerDSL
public fun onUpdate(update: MenuPlayerUpdateEvent) {
eventHandler.updateCallbacks.add(update)
}
public fun onClose(close: MenuPlayerCloseEvent) {
eventHandler.closeCallbacks.add(close)
}
public fun onMoveToMenu(moveToMenu: MenuPlayerMoveToEvent) {
eventHandler.moveToMenuCallbacks.add(moveToMenu)
}
public fun preOpen(preOpen: MenuPlayerPreOpenEvent) {
eventHandler.preOpenCallbacks.add(preOpen)
}
public fun onOpen(open: MenuPlayerOpenEvent) {
eventHandler.openCallbacks.add(open)
}
}
@@ -0,0 +1,199 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl
import tech.carcadex.kotlinbukkitkit.architecture.extensions.WithPlugin
import tech.carcadex.kotlinbukkitkit.extensions.task
import tech.carcadex.kotlinbukkitkit.menu.MenuControllerPlugins
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerClose
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerOpen
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerPreOpen
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerUpdate
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot.SlotDSL
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot.SlotDSLImpl
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot.SlotEventHandlerDSL
import tech.carcadex.kotlinbukkitkit.menu.rangeOfSlots
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotRender
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotUpdate
import tech.carcadex.kotlinbukkitkit.menu.slot.Slot
import tech.carcadex.kotlinbukkitkit.menu.slotOrBaseSlot
import tech.carcadex.kotlinbukkitkit.menu.viewersFromPlayers
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
import java.util.*
public inline fun WithPlugin<*>.menu(
displayName: String,
lines: Int,
cancelOnClick: Boolean = true,
block: MenuDSL.() -> Unit,
): MenuDSL = plugin.menu(displayName, lines, cancelOnClick, block)
public inline fun Plugin.menu(
displayName: String,
lines: Int,
cancelOnClick: Boolean = true,
block: MenuDSL.() -> Unit,
): MenuDSL = menu(displayName, lines, this, cancelOnClick, block)
public inline fun menu(
displayName: String,
lines: Int,
plugin: Plugin,
cancelOnClick: Boolean = true,
block: MenuDSL.() -> Unit,
): MenuDSL = MenuDSLImpl(plugin, displayName, lines, cancelOnClick).apply(block)
public class MenuDSLImpl(
public override val plugin: Plugin,
override var title: String,
override var lines: Int,
override var cancelOnClick: Boolean,
) : MenuDSL {
init {
MenuControllerPlugins.ensureInitialized(plugin)
}
private var task: BukkitTask? = null
override var updateDelay: Long = 0
set(value) {
field = value
removeTask()
if (value > 0 && viewers.isNotEmpty()) {
setNewTask()
}
}
override val viewers: MutableMap<Player, Inventory> = WeakHashMap()
override val slots: TreeMap<Int, SlotDSL> = TreeMap()
override val data: WeakHashMap<String, Any> = WeakHashMap<String, Any>()
override val playerData: WeakHashMap<Player, WeakHashMap<String, Any>> = WeakHashMap<Player, WeakHashMap<String, Any>>()
override val eventHandler: MenuEventHandlerDSL = MenuEventHandlerDSL(this)
override var baseSlot: SlotDSL = SlotDSLImpl(null, cancelOnClick, SlotEventHandlerDSL())
override fun setSlot(slot: Int, slotObj: SlotDSL) {
slots.put(slot, slotObj)
}
override fun update(players: Set<Player>) {
val viewers = viewersFromPlayers(players)
for ((player, inventory) in viewers) {
val update = MenuPlayerUpdate(this, player, inventory, title)
eventHandler.update(update)
// TODO title
/*if(update.title != title)*/
for (i in rangeOfSlots()) {
val slot = slotOrBaseSlot(i)
updateSlotOnlyPos(i, slot, player, inventory)
}
}
}
override fun updateSlot(slot: Slot, players: Set<Player>) {
val slots: Map<Int, SlotDSL> = if (slot === baseSlot) {
rangeOfSlots().mapNotNull { if (slots[it] == null) it to slot else null }.toMap()
} else {
rangeOfSlots().mapNotNull { if (slot === slots[it]) it to slot else null }.toMap()
}
for ((player, inventory) in viewersFromPlayers(players)) {
for ((pos, s) in slots) {
updateSlotOnlyPos(pos, s, player, inventory)
}
}
}
private fun updateSlotOnlyPos(pos: Int, slot: Slot, player: Player, inventory: Inventory) {
val slotUpdate = MenuPlayerSlotUpdate(this, pos, slot, player, inventory)
slot.eventHandler.update(slotUpdate)
}
override fun openToPlayer(vararg players: Player) {
for (player in players) {
close(player, false)
try {
val inv = inventory
viewers.put(player, inv)
val preOpen = MenuPlayerPreOpen(this, player)
eventHandler.preOpen(preOpen)
if (preOpen.canceled) return
for (i in rangeOfSlots()) {
val slot = slotOrBaseSlot(i)
val render = MenuPlayerSlotRender(this, i, slot, player, inv)
slot.eventHandler.render(render)
}
player.openInventory(inv)
val open = MenuPlayerOpen(this, player, inventory)
eventHandler.open(open)
if (task == null && updateDelay > 0 && viewers.isNotEmpty()) {
setNewTask()
}
} catch (e: Throwable) {
e.printStackTrace()
removePlayer(player, true)
}
}
}
override fun getInventory(): Inventory {
val slots = rangeOfSlots()
val inventory = Bukkit.createInventory(this, slots.endInclusive, title)
for (i in slots) {
val slot = slotOrBaseSlot(i)
val item = slot.item?.clone()
inventory.setItem(i - 1, item)
}
return inventory
}
private fun removePlayer(player: Player, closeInventory: Boolean): Boolean {
if (closeInventory) player.closeInventory()
val viewing = viewers.remove(player) != null
if (viewing) {
clearPlayerData(player)
}
return viewing
}
override fun close(player: Player, closeInventory: Boolean) {
if (removePlayer(player, closeInventory)) {
val menuClose = MenuPlayerClose(this, player)
eventHandler.close(menuClose)
if (task != null && updateDelay > 0 && viewers.isEmpty()) {
removeTask()
}
}
}
private fun setNewTask() {
task = task(repeatDelay = updateDelay) { update() }
}
private fun removeTask() {
task?.cancel()
task = null
}
}
@@ -0,0 +1,54 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl
import tech.carcadex.kotlinbukkitkit.menu.MenuEventHandler
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerClose
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerMoveTo
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerOpen
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerPreOpen
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerUpdate
public typealias MenuPlayerUpdateEvent = MenuPlayerUpdate.() -> Unit
public typealias MenuPlayerCloseEvent = MenuPlayerClose.() -> Unit
public typealias MenuPlayerMoveToEvent = MenuPlayerMoveTo.() -> Unit
public typealias MenuPlayerPreOpenEvent = MenuPlayerPreOpen.() -> Unit
public typealias MenuPlayerOpenEvent = MenuPlayerOpen.() -> Unit
public class MenuEventHandlerDSL(public val menu: MenuDSL) : MenuEventHandler {
internal val updateCallbacks: MutableList<MenuPlayerUpdateEvent> = mutableListOf()
internal val closeCallbacks: MutableList<MenuPlayerCloseEvent> = mutableListOf()
internal val moveToMenuCallbacks: MutableList<MenuPlayerMoveToEvent> = mutableListOf()
internal val preOpenCallbacks: MutableList<MenuPlayerPreOpenEvent> = mutableListOf()
internal val openCallbacks: MutableList<MenuPlayerOpenEvent> = mutableListOf()
override fun update(update: MenuPlayerUpdate) {
for (callback in updateCallbacks) {
callback(update)
}
}
override fun close(close: MenuPlayerClose) {
for (callback in closeCallbacks) {
callback(close)
}
}
override fun moveToMenu(moveToMenu: MenuPlayerMoveTo) {
for (callback in moveToMenuCallbacks) {
callback(moveToMenu)
}
}
override fun preOpen(preOpen: MenuPlayerPreOpen) {
for (callback in preOpenCallbacks) {
callback(preOpen)
}
}
override fun open(open: MenuPlayerOpen) {
for (callback in openCallbacks) {
callback(open)
}
}
}
@@ -0,0 +1,134 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.pagination
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayer
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerInventory
import tech.carcadex.kotlinbukkitkit.menu.dsl.MenuDSL
import tech.carcadex.kotlinbukkitkit.menu.dsl.pagination.slot.PaginationSlotDSL
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot.SlotDSL
import tech.carcadex.kotlinbukkitkit.utility.collections.ObservableCollection
import org.bukkit.entity.Player
import java.lang.IllegalArgumentException
import java.util.TreeMap
import java.util.WeakHashMap
public typealias ItemsProvider<T> = () -> ObservableCollection<T>
public typealias ItemsAdapter<T> = MenuPlayer.(List<T>) -> List<T>
public enum class Orientation { HORIZONTAL, VERTICAL }
public inline fun <T> MenuPagination<T>.slot(
builder: PaginationSlotDSL<T>.() -> Unit,
) {
for (paginationSlot in paginationSlots.values) {
paginationSlot.builder()
}
}
public fun MenuDSL.setPlayerOpenPage(player: Player, page: Int) {
playerData.put(
player,
WeakHashMap<String, Any>().apply { put(PAGINATION_OPEN_PAGE_KEY, page) },
)
}
public inline fun <T> MenuDSL.pagination(
itemsProvider: ObservableCollection<T>,
nextPageSlot: SlotDSL,
previousPageSlot: SlotDSL,
autoUpdateSwitchPageSlot: Boolean = true,
startLine: Int = 1,
endLine: Int = lines - 1,
startSlot: Int = 1,
endSlot: Int = 9,
orientation: Orientation = Orientation.HORIZONTAL,
noinline itemsAdapterOnOpen: ItemsAdapter<T>? = null,
noinline itemsAdapterOnUpdate: ItemsAdapter<T>? = null,
builder: MenuPaginationImpl<T>.() -> Unit,
): MenuPaginationImpl<T> {
return pagination(
{ itemsProvider },
nextPageSlot,
previousPageSlot,
autoUpdateSwitchPageSlot,
startLine,
endLine,
startSlot,
endSlot,
orientation,
itemsAdapterOnOpen,
itemsAdapterOnUpdate,
builder,
)
}
public inline fun <T> MenuDSL.pagination(
noinline itemsProvider: ItemsProvider<T>,
nextPageSlot: SlotDSL,
previousPageSlot: SlotDSL,
autoUpdateSwitchPageSlot: Boolean = true,
startLine: Int = 1,
endLine: Int = lines - 1,
startSlot: Int = 1,
endSlot: Int = 9,
orientation: Orientation = Orientation.HORIZONTAL,
noinline itemsAdapterOnOpen: ItemsAdapter<T>? = null,
noinline itemsAdapterOnUpdate: ItemsAdapter<T>? = null,
builder: MenuPaginationImpl<T>.() -> Unit,
): MenuPaginationImpl<T> {
if (startSlot > endSlot) throw IllegalArgumentException()
if (startLine > endLine) throw IllegalArgumentException()
return MenuPaginationImpl(
this,
itemsProvider,
nextPageSlot,
previousPageSlot,
autoUpdateSwitchPageSlot,
startLine,
endLine,
startSlot,
endSlot,
orientation,
itemsAdapterOnOpen,
itemsAdapterOnUpdate,
).apply(builder)
}
public interface MenuPagination<T> {
public val menu: MenuDSL
public val paginationSlots: TreeMap<Int, PaginationSlotDSL<T>>
public val paginationEventHandler: PaginationEventHandler
public val itemsProvider: ItemsProvider<T>
public val nextPageSlot: SlotDSL
public val previousPageSlot: SlotDSL
public val autoUpdateSwitchPageSlot: Boolean
public val startLine: Int
public val endLine: Int
public val startSlot: Int
public val endSlot: Int
public val orientation: Orientation
public val itemsAdapterOnOpen: ItemsAdapter<T>?
public val itemsAdapterOnUpdate: ItemsAdapter<T>?
public fun onPageChange(pageChange: MenuPlayerPageChangeEvent) {
paginationEventHandler.pageChangeCallbacks.add(pageChange)
}
public fun onPageAvailable(pageAvailable: MenuPlayerPageAvailableEvent) {
paginationEventHandler.pageAvailableCallbacks.add(pageAvailable)
}
public fun hasPreviousPage(player: Player): Boolean
public fun hasNextPage(player: Player): Boolean
public fun getPlayerCurrentPage(player: Player): Int
public fun updateItemsToPlayer(menuPlayer: MenuPlayerInventory)
}
@@ -0,0 +1,265 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.pagination
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerInventory
import tech.carcadex.kotlinbukkitkit.menu.calculateSlot
import tech.carcadex.kotlinbukkitkit.menu.dsl.MenuDSL
import tech.carcadex.kotlinbukkitkit.menu.dsl.pagination.slot.PaginationSlotDSL
import tech.carcadex.kotlinbukkitkit.menu.dsl.pagination.slot.PaginationSlotDSLImpl
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot.SlotDSL
import org.bukkit.entity.Player
import java.util.*
internal const val PAGINATION_OPEN_PAGE_KEY = "PAGINATION:open_page"
public class MenuPaginationImpl<T>(
override val menu: MenuDSL,
override val itemsProvider: ItemsProvider<T>,
override val nextPageSlot: SlotDSL,
override val previousPageSlot: SlotDSL,
override val autoUpdateSwitchPageSlot: Boolean,
override val startLine: Int = 1,
override val endLine: Int = menu.lines - 1,
override val startSlot: Int = 1,
override val endSlot: Int = 9,
override val orientation: Orientation = Orientation.HORIZONTAL,
override val itemsAdapterOnOpen: ItemsAdapter<T>? = null,
override val itemsAdapterOnUpdate: ItemsAdapter<T>? = null,
) : MenuPagination<T> {
override val paginationEventHandler: PaginationEventHandler = PaginationEventHandler()
override val paginationSlots: TreeMap<Int, PaginationSlotDSL<T>> = TreeMap()
internal val currentPlayerPage = WeakHashMap<Player, Int>()
// should be null when is not mapped (for not allocate a new list everytime)
internal val currentPlayerItems = WeakHashMap<Player, List<T>>()
internal val itemSlotData = WeakHashMap<T, WeakHashMap<String, Any>>()
internal val itemPlayerSlotData = WeakHashMap<T, WeakHashMap<Player, WeakHashMap<String, Any>>>()
init {
// setup next page button click
nextPageSlot.onClick {
if (hasNextPage(player)) {
nextPage(this)
}
}
// setup previous page button click
previousPageSlot.onClick {
if (hasPreviousPage(player)) {
previousPage(this)
}
}
menu.preOpen {
// adapting items for player
val items = itemsAdapterOnOpen?.let { it(getPlayerItems(player).toList()) }
if (items != null) {
currentPlayerItems[player] = items
}
// set the menu page if player informed in [MenuDSL.setPlayerOpenPage]
val openPage = menu.playerData.get(player)?.get(PAGINATION_OPEN_PAGE_KEY) as? Int?
if (openPage != null) {
// check if openPage is valid or cancel and trigger a event
if (isPageAvailable(player, openPage)) {
currentPlayerPage[player] = openPage
} else {
canceled = true
paginationEventHandler.pageAvailable(this)
}
}
}
// cleaning player data on menu close
menu.onClose {
currentPlayerPage.remove(player)
currentPlayerItems.remove(player)
for (value in itemPlayerSlotData.values) {
value.remove(player)
}
}
// if enabled, update next and previous slots when page change
if (autoUpdateSwitchPageSlot) {
onPageChange {
menu.updateSlot(nextPageSlot, player)
menu.updateSlot(previousPageSlot, player)
}
}
// setup pagination slots
for (line in startLine..endLine) {
for (slotPos in startSlot..endSlot) {
val menuSlot = calculateSlot(line, slotPos)
val slotRoot = menu.slot(menuSlot, null)
val paginationSlot = PaginationSlotDSLImpl(
this,
slotRoot,
)
slotRoot.apply {
fun getCurrentItemForPlayer(player: Player): T? {
return getCurrentItemForPlayer(
menuSlot,
getPlayerCurrentPage(player),
player,
)
}
onRender {
paginationSlot.paginationEventHandler.handleRender(
getCurrentItemForPlayer(player),
this,
)
}
onUpdate {
paginationSlot.paginationEventHandler.handleUpdate(
getCurrentItemForPlayer(player),
this,
)
}
onClick {
paginationSlot.paginationEventHandler.handleInteract(
getCurrentItemForPlayer(player),
this,
)
}
}
paginationSlots.put(menuSlot, paginationSlot)
}
}
}
override fun hasPreviousPage(player: Player): Boolean {
return isPageAvailable(player, getPlayerCurrentPage(player) - 1)
}
override fun hasNextPage(player: Player): Boolean {
return isPageAvailable(player, getPlayerCurrentPage(player) + 1)
}
override fun getPlayerCurrentPage(player: Player): Int = currentPlayerPage.getOrDefault(player, 1)
/**
* Update current item list calling itemsUpdateFilter
*/
override fun updateItemsToPlayer(menuPlayer: MenuPlayerInventory) {
val player = menuPlayer.player
// checking if adapter is not null, other wise, ignore
if (itemsAdapterOnUpdate != null) {
val items = itemsAdapterOnUpdate.let { menuPlayer.it(itemsProvider().toList()) }
currentPlayerItems[player] = items
// setting player to the last page if he is not in a page with items after adapting
if (!isPageAvailable(player, getPlayerCurrentPage(player))) {
currentPlayerPage[player] = maxPages(player)
}
// calling update slot to the PaginationSlotDSL
forEachSlot { slotPos, pageSlot ->
val currentPage = getPlayerCurrentPage(player)
val item: T? = getCurrentItemForPlayer(slotPos, currentPage, player)
pageSlot.updateSlot(item, item, slotPos, menuPlayer)
}
// calling event because this remove items causing no more page needed
paginationEventHandler.pageChange(menuPlayer)
}
}
internal fun getCurrentItemForPlayer(slotPos: Int, page: Int, player: Player): T? {
val items = getPlayerItems(player)
val startLineNotUsage = startLine - 1
val lineEndSlotNotUsage = 9 - endSlot
val startSlotNotUsage = startSlot - 1
val startLineSlotsUsage = startLineNotUsage * 9
val currentLine = (slotPos / 9) + 1
val currentLineUsage = currentLine - startLineNotUsage
val currentUsageStartSlots = startSlotNotUsage * currentLineUsage
val currentEndSlotUsage = if (currentLineUsage > 1) lineEndSlotNotUsage * (currentLineUsage - 1) else 0
val slotItemIndex = pageStartIndex(page) + (slotPos - startLineSlotsUsage - currentUsageStartSlots - currentEndSlotUsage)
return items.asSequence().elementAtOrNull(slotItemIndex - 1)
}
internal fun nextPage(menuPlayerInventory: MenuPlayerInventory) {
val nextPage = getPlayerCurrentPage(menuPlayerInventory.player) + 1
changePage(menuPlayerInventory, nextPage)
}
internal fun previousPage(menuPlayerInventory: MenuPlayerInventory) {
val previousPage = getPlayerCurrentPage(menuPlayerInventory.player) - 1
changePage(menuPlayerInventory, previousPage)
}
private fun changePage(menuPlayerInventory: MenuPlayerInventory, nextPage: Int) {
val player = menuPlayerInventory.player
val currentPage = getPlayerCurrentPage(player)
currentPlayerPage[player] = nextPage
forEachSlot { slotPos, pageSlot ->
val actualItem: T? = getCurrentItemForPlayer(slotPos, currentPage, player)
val nextItem: T? = getCurrentItemForPlayer(slotPos, nextPage, player)
pageSlot.updateSlot(actualItem, nextItem, slotPos, menuPlayerInventory, true)
paginationEventHandler.pageChange(menuPlayerInventory)
}
}
private inline fun forEachSlot(
block: (slotPos: Int, pageSlot: PaginationSlotDSLImpl<T>) -> Unit,
) {
for (line in startLine..endLine) {
for (slot in startSlot..endSlot) {
val slotPos = calculateSlot(line, slot)
val pageSlot = paginationSlots[slotPos] as? PaginationSlotDSLImpl<T>
?: continue
block(slotPos, pageSlot)
}
}
}
private fun pageStartIndex(page: Int) = ((page - 1) * maxSlotPerPage())
private fun getPlayerItems(player: Player) = currentPlayerItems[player] ?: itemsProvider()
private fun maxSlotPerPage() = (endLine - startLine + 1) * (endSlot - startSlot + 1)
private fun maxPages(player: Player): Int {
val itemsCount = getPlayerItems(player).size
val maxPerPage = maxSlotPerPage()
var pages = (itemsCount / maxPerPage).toInt()
val mod = itemsCount % maxPerPage
if (mod > 0) pages++
return pages
}
private fun isPageAvailable(player: Player, page: Int): Boolean {
val maxPages = maxPages(player)
return page in 1..maxPages
}
}
@@ -0,0 +1,23 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.pagination
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayer
public typealias MenuPlayerPageChangeEvent = MenuPlayer.() -> Unit
public typealias MenuPlayerPageAvailableEvent = MenuPlayer.() -> Unit
public class PaginationEventHandler {
internal val pageChangeCallbacks: MutableList<MenuPlayerPageChangeEvent> = mutableListOf()
internal val pageAvailableCallbacks: MutableList<MenuPlayerPageAvailableEvent> = mutableListOf()
public fun pageChange(pageChange: MenuPlayer) {
for (callback in pageChangeCallbacks) {
callback(pageChange)
}
}
public fun pageAvailable(pageAvailable: MenuPlayer) {
for (callback in pageAvailableCallbacks) {
callback(pageAvailable)
}
}
}
@@ -0,0 +1,35 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.pagination.slot
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot.SlotDSL
import org.bukkit.entity.Player
import java.util.*
public interface PaginationSlotDSL<T> {
public val slotRoot: SlotDSL
public val paginationEventHandler: PaginationSlotEventHandler<T>
public val slotData: WeakHashMap<String, Any>
public val playerSlotData: WeakHashMap<Player, WeakHashMap<String, Any>>
/**
* Cancel the interaction with this slot
*/
public var cancel: Boolean
public fun onPageChange(pageChange: MenuPlayerSlotPageChangeEvent<T>) {
paginationEventHandler.pageChangeCallbacks.add(pageChange)
}
public fun onClick(click: MenuPlayerPageSlotInteractEvent<T>) {
paginationEventHandler.interactCallbacks.add(click)
}
public fun onRender(render: MenuPlayerPageSlotRenderEvent<T>) {
paginationEventHandler.renderCallbacks.add(render)
}
public fun onUpdate(update: MenuPlayerPageSlotUpdateEvent<T>) {
paginationEventHandler.updateCallbacks.add(update)
}
}
@@ -0,0 +1,94 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.pagination.slot
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerInventory
import tech.carcadex.kotlinbukkitkit.menu.dsl.pagination.MenuPaginationImpl
import tech.carcadex.kotlinbukkitkit.menu.dsl.slot.SlotDSL
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotRender
import org.bukkit.entity.Player
import java.util.*
public class PaginationSlotDSLImpl<T>(
private val pagination: MenuPaginationImpl<T>,
override val slotRoot: SlotDSL,
) : PaginationSlotDSL<T> {
override val paginationEventHandler: PaginationSlotEventHandler<T> = PaginationSlotEventHandler<T>()
override var cancel: Boolean
get() = slotRoot.cancel
set(value) { slotRoot.cancel = value }
override val slotData: WeakHashMap<String, Any>
get() = slotRoot.slotData
override val playerSlotData: WeakHashMap<Player, WeakHashMap<String, Any>>
get() = slotRoot.playerSlotData
internal fun updateSlot(
actualItem: T?,
nextItem: T?,
slotPos: Int,
menuPlayerInventory: MenuPlayerInventory,
isPageChange: Boolean = false,
) {
if (isPageChange) {
relocateSlotData(actualItem, nextItem)
// triggering event
paginationEventHandler.handlePageChange(
actualItem,
MenuPlayerSlotPageChange(
pagination.menu,
slotPos,
slotRoot,
menuPlayerInventory.player,
menuPlayerInventory.inventory,
),
)
}
// cleaning item in the inventory slot
menuPlayerInventory.setItem(slotPos, null)
paginationEventHandler.handleRender(
nextItem,
MenuPlayerSlotRender(
pagination.menu,
slotPos,
slotRoot,
menuPlayerInventory.player,
menuPlayerInventory.inventory,
),
)
}
internal fun relocateSlotData(actualItem: T?, nextItem: T?) {
if (actualItem != null) {
// caching the current Data from Slot
val slotData = WeakHashMap(slotData)
val playerSlotData = WeakHashMap(playerSlotData)
if (slotData.isNotEmpty()) {
pagination.itemSlotData[actualItem] = slotData
}
if (playerSlotData.isNotEmpty()) {
pagination.itemPlayerSlotData[actualItem] = playerSlotData
}
}
// cleaning current data in the Slot
slotData.clear()
playerSlotData.clear()
if (nextItem != null) {
val nextSlotData = pagination.itemSlotData[nextItem]
val nextPlayerSlotData = pagination.itemPlayerSlotData[nextItem]
if (nextSlotData != null) {
slotData.putAll(nextSlotData)
}
if (nextPlayerSlotData != null) {
playerSlotData.putAll(nextPlayerSlotData)
}
}
}
}
@@ -0,0 +1,54 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.pagination.slot
import tech.carcadex.kotlinbukkitkit.menu.Menu
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerInventorySlot
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotInteract
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotRender
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotUpdate
import tech.carcadex.kotlinbukkitkit.menu.slot.Slot
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
public typealias MenuPlayerSlotPageChangeEvent<T> = MenuPlayerSlotPageChange.(T?) -> Unit
public typealias MenuPlayerPageSlotInteractEvent<T> = MenuPlayerSlotInteract.(T?) -> Unit
public typealias MenuPlayerPageSlotRenderEvent<T> = MenuPlayerSlotRender.(T?) -> Unit
public typealias MenuPlayerPageSlotUpdateEvent<T> = MenuPlayerSlotUpdate.(T?) -> Unit
public class PaginationSlotEventHandler<T> {
internal val pageChangeCallbacks = mutableListOf<MenuPlayerSlotPageChangeEvent<T>>()
internal val interactCallbacks = mutableListOf<MenuPlayerPageSlotInteractEvent<T>>()
internal val renderCallbacks = mutableListOf<MenuPlayerPageSlotRenderEvent<T>>()
internal val updateCallbacks = mutableListOf<MenuPlayerPageSlotUpdateEvent<T>>()
public fun handlePageChange(currentItem: T?, pageChange: MenuPlayerSlotPageChange) {
for (callback in pageChangeCallbacks) {
callback(pageChange, currentItem)
}
}
public fun handleRender(currentItem: T?, render: MenuPlayerSlotRender) {
for (callback in renderCallbacks) {
callback(render, currentItem)
}
}
public fun handleUpdate(currentItem: T?, update: MenuPlayerSlotUpdate) {
for (callback in updateCallbacks) {
callback(update, currentItem)
}
}
public fun handleInteract(currentItem: T?, interact: MenuPlayerSlotInteract) {
for (callback in interactCallbacks) {
callback(interact, currentItem)
}
}
}
public class MenuPlayerSlotPageChange(
override val menu: Menu<*>,
override val slotPos: Int,
override val slot: Slot,
override val player: Player,
override val inventory: Inventory,
) : MenuPlayerInventorySlot
@@ -0,0 +1,27 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.slot
import tech.carcadex.kotlinbukkitkit.menu.slot.Slot
import org.bukkit.inventory.ItemStack
public interface SlotDSL : Slot {
override val eventHandler: SlotEventHandlerDSL
public fun onClick(click: MenuPlayerSlotInteractEvent) {
eventHandler.interactCallbacks.add(click)
}
public fun onRender(render: MenuPlayerSlotRenderEvent) {
eventHandler.renderCallbacks.add(render)
}
public fun onUpdate(update: MenuPlayerSlotUpdateEvent) {
eventHandler.updateCallbacks.add(update)
}
public fun onMoveToSlot(moveToSlot: MenuPlayerSlotMoveToEvent) {
eventHandler.moveToSlotCallbacks.add(moveToSlot)
}
override fun clone(item: ItemStack?): SlotDSL
}
@@ -0,0 +1,22 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.slot
import tech.carcadex.kotlinbukkitkit.menu.dsl.MenuDSL
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import java.util.*
public fun MenuDSL.newSlot(item: ItemStack?, builder: SlotDSL.() -> Unit): SlotDSL {
return SlotDSLImpl(item, cancelOnClick).apply(builder)
}
public class SlotDSLImpl(
override val item: ItemStack?,
override var cancel: Boolean,
override val eventHandler: SlotEventHandlerDSL = SlotEventHandlerDSL(),
) : SlotDSL {
override val slotData: WeakHashMap<String, Any> = WeakHashMap()
override val playerSlotData: WeakHashMap<Player, WeakHashMap<String, Any>> = WeakHashMap()
override fun clone(item: ItemStack?): SlotDSL = SlotDSLImpl(item, cancel, eventHandler.clone())
}
@@ -0,0 +1,53 @@
package tech.carcadex.kotlinbukkitkit.menu.dsl.slot
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotInteract
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotMoveTo
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotRender
import tech.carcadex.kotlinbukkitkit.menu.slot.MenuPlayerSlotUpdate
import tech.carcadex.kotlinbukkitkit.menu.slot.SlotEventHandler
public typealias MenuPlayerSlotInteractEvent = MenuPlayerSlotInteract.() -> Unit
public typealias MenuPlayerSlotRenderEvent = MenuPlayerSlotRender.() -> Unit
public typealias MenuPlayerSlotUpdateEvent = MenuPlayerSlotUpdate.() -> Unit
public typealias MenuPlayerSlotMoveToEvent = MenuPlayerSlotMoveTo.() -> Unit
public class SlotEventHandlerDSL : SlotEventHandler {
internal val interactCallbacks = mutableListOf<MenuPlayerSlotInteractEvent>()
internal val renderCallbacks = mutableListOf<MenuPlayerSlotRenderEvent>()
internal val updateCallbacks = mutableListOf<MenuPlayerSlotUpdateEvent>()
internal val moveToSlotCallbacks = mutableListOf<MenuPlayerSlotMoveToEvent>()
override fun interact(interact: MenuPlayerSlotInteract) {
for (callback in interactCallbacks) {
callback(interact)
}
}
override fun render(render: MenuPlayerSlotRender) {
for (callback in renderCallbacks) {
callback(render)
}
}
override fun update(update: MenuPlayerSlotUpdate) {
for (callback in updateCallbacks) {
callback(update)
}
}
override fun moveToSlot(moveToSlot: MenuPlayerSlotMoveTo) {
for (callback in moveToSlotCallbacks) {
callback(moveToSlot)
}
}
override fun clone(): SlotEventHandlerDSL {
return SlotEventHandlerDSL().also {
it.interactCallbacks.addAll(interactCallbacks)
it.renderCallbacks.addAll(renderCallbacks)
it.updateCallbacks.addAll(updateCallbacks)
it.moveToSlotCallbacks.addAll(moveToSlotCallbacks)
}
}
}
@@ -0,0 +1,33 @@
package tech.carcadex.kotlinbukkitkit.menu.slot
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import java.util.*
public interface Slot {
public val item: ItemStack?
public val eventHandler: SlotEventHandler
public val slotData: WeakHashMap<String, Any>
public val playerSlotData: WeakHashMap<Player, WeakHashMap<String, Any>>
/**
* Cancel the interaction with this slot
*/
public var cancel: Boolean
/**
* a clone of Slot without slotData and playerSlotData
*/
public fun clone(item: ItemStack?): Slot
public fun clearSlotData() {
slotData.clear()
}
public fun clearPlayerData(player: Player) {
playerSlotData.remove(player)
}
}
@@ -0,0 +1,14 @@
package tech.carcadex.kotlinbukkitkit.menu.slot
public interface SlotEventHandler {
public fun interact(interact: MenuPlayerSlotInteract)
public fun render(render: MenuPlayerSlotRender)
public fun update(update: MenuPlayerSlotUpdate)
public fun moveToSlot(moveToSlot: MenuPlayerSlotMoveTo)
public fun clone(): SlotEventHandler
}
@@ -0,0 +1,85 @@
package tech.carcadex.kotlinbukkitkit.menu.slot
import tech.carcadex.kotlinbukkitkit.menu.Menu
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayer
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerInteract
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerInventory
import tech.carcadex.kotlinbukkitkit.menu.MenuPlayerMove
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.inventory.ClickType
import org.bukkit.event.inventory.InventoryAction
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
import java.util.WeakHashMap
public val MenuPlayerSlot.rawSlotPos: Int get() = slotPos - 1
public interface MenuPlayerSlot : MenuPlayer {
public val slotPos: Int
public val slot: Slot
public fun putPlayerSlotData(key: String, value: Any) {
slot.playerSlotData.getOrPut(player) {
WeakHashMap()
}[key] = value
}
public fun getPlayerSlotData(key: String): Any? = slot.playerSlotData.get(player)?.get(key)
}
public interface MenuPlayerInventorySlot : MenuPlayerSlot, MenuPlayerInventory {
public var showingItem: ItemStack?
get() = getItem(slotPos)?.takeUnless { it.type == Material.AIR }
set(value) = setItem(slotPos, value)
public fun updateSlotToPlayer() {
menu.updateSlot(slot, player)
}
public fun updateSlot() {
menu.updateSlot(slot)
}
}
public open class MenuPlayerSlotInteract(
menu: Menu<*>,
override val slotPos: Int,
override val slot: Slot,
player: Player,
inventory: Inventory,
canceled: Boolean,
public val click: ClickType,
public val action: InventoryAction,
public val clicked: ItemStack?,
public val cursor: ItemStack?,
public val hotbarKey: Int,
) : MenuPlayerInteract(menu, player, inventory, canceled), MenuPlayerInventorySlot
public class MenuPlayerSlotMoveTo(
override val menu: Menu<*>,
override val slotPos: Int,
override val slot: Slot,
override val player: Player,
override val inventory: Inventory,
override var canceled: Boolean,
override val toMoveSlot: Int,
override var toMoveItem: ItemStack?,
) : MenuPlayerInventorySlot, MenuPlayerMove
public class MenuPlayerSlotRender(
override val menu: Menu<*>,
override val slotPos: Int,
override val slot: Slot,
override val player: Player,
override val inventory: Inventory,
) : MenuPlayerInventorySlot
public class MenuPlayerSlotUpdate(
override val menu: Menu<*>,
override val slotPos: Int,
override val slot: Slot,
override val player: Player,
override val inventory: Inventory,
) : MenuPlayerInventorySlot
@@ -38,12 +38,12 @@ public interface Message extends ComponentLike {
@NotNull
List<Component> asComponentList();
/**
* Get unparsed value of message
* @return unparsed value of message
*/
@NotNull
List<String> asUnparsedStringList();
/**
* Get unparsed value of message joined to one string
* @return unparsed value of message joined to one string
*/
@NotNull
String asUnparsedString(@NotNull String newLine);
+4
View File
@@ -7,3 +7,7 @@ include("serialization")
include("genref")
include("messages")
include("commands")
include("utility")
include("exposed")
include("thirdparty")
include("menu")
+42
View File
@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+116
View File
@@ -0,0 +1,116 @@
plugins {
id("java")
kotlin("jvm")
kotlin("plugin.serialization") version "1.8.21"
id("com.github.johnrengelman.shadow") version "7.1.2"
id("maven-publish")
id("signing")
}
group = "tech.carcadex"
val globalArtifactId = "kotlinbukkitkit-thirdparty"
val globalName = "kotlinbukkitkit-thirdparty"
repositories {
mavenCentral()
maven {
name = "spigotmc-repo"
url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
}
}
dependencies {
compileOnly("org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT")
api(project(":architecture"))
api(project(":utility"))
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(8)
}
kotlin {
sourceSets.main {
kotlin.srcDir("build/generated/ksp/main/kotlin")
}
sourceSets.test {
kotlin.srcDir("build/generated/ksp/test/kotlin")
}
}
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = globalArtifactId
groupId = "tech.carcadex"
version = version
from(components["java"])
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
pom {
artifactId = globalArtifactId
groupId = "tech.carcadex"
version = version
name.set(globalArtifactId)
description.set(globalArtifactId)
url.set("https://github.com/CarcadeX/KotlinBukkitKit")
licenses {
license {
name.set("MIT")
url.set("https://mit-license.org/")
}
}
developers {
developer {
id.set("itzRedTea")
name.set("Red Tea")
email.set("red__tea@outlook.com")
}
}
scm {
connection.set("scm:https://github.com/CarcadeX/$globalName.git")
developerConnection.set("git@github.com:CarcadeX/$globalName.git")
url.set("https://github.com/CarcadeX/KotlinBukkitKit")
}
}
}
}
repositories {
maven {
val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
credentials {
username = "itzRedTea"
password = project.findProperty("password").toString()
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications.getByName("maven"))
}
@@ -0,0 +1,55 @@
package tech.carcadex.kotlinbukkitkit.bungeecord
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import org.bukkit.plugin.messaging.PluginMessageListener
import java.nio.ByteBuffer
import java.nio.charset.Charset
object BungeeCordController: PluginMessageListener {
private val queue = mutableListOf<BungeeCordRequest>()
private var initialized = false;
private var plugin: Plugin? = null
fun init(plugin: Plugin) {
if(initialized) return
this.plugin = plugin
Bukkit.getServer().messenger.registerOutgoingPluginChannel(plugin, "BungeeCord")
Bukkit.getServer().messenger.registerIncomingPluginChannel(plugin, "BungeeCord", this)
initialized = true
}
fun Plugin.initBungeeCordController() {
init(this)
}
override fun onPluginMessageReceived(channel: String, player: Player, message: ByteArray) {
if (channel != "BungeeCord") return
val buffer = ByteBuffer.wrap(message)
val subChannel = buffer.readUTF()
val request = queue.firstOrNull { it.subChannel == subChannel }
if(request?.responseCallback != null) {
val infoBuffer = buffer.slice()
val info = ByteArray(infoBuffer.remaining())
infoBuffer.get(info)
request.responseCallback.invoke(info)
queue.remove(request)
}
}
fun sendBungeeCord(player: Player, message: ByteArray) {
if(!initialized) {
Bukkit.getLogger().severe("Could not execute sendBungeeCord method, because BungeeCordController" +
" has not been initialized!")
}
player.sendPluginMessage(plugin!!, "BungeeCord", message)
}
fun addToQueue(request: BungeeCordRequest) = queue.add(request)
private fun ByteBuffer.readUTF() = String(ByteArray(short.toInt()).apply { get(this) }, Charset.forName("UTF-8"))
}
@@ -0,0 +1,23 @@
package tech.carcadex.kotlinbukkitkit.bungeecord
import com.google.common.io.ByteStreams
import org.bukkit.entity.Player
typealias ResponseCallback = (message: ByteArray) -> Unit
class BungeeCordRequest(
val player: Player,
val subChannel: String,
val request: ByteArray? = null,
val responseCallback: ResponseCallback? = null
) {
fun send() {
val out = ByteStreams.newDataOutput()
out.writeUTF(subChannel)
if (request != null) out.write(request)
player.sendBungeeCord(out.toByteArray())
if (responseCallback != null) BungeeCordController.addToQueue(this)
}
}
@@ -0,0 +1,58 @@
package tech.carcadex.kotlinbukkitkit.bungeecord
import org.bukkit.entity.Player
import com.google.common.io.ByteStreams
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
// TODO: replace controller with KotlinPlugin lifecycle extension
public val Player.bungeecord get() = BungeeCord(this)
public fun Player.sendBungeeCord(message: ByteArray) = BungeeCordController.sendBungeeCord(this, message)
@JvmInline
public value class BungeeCord(private val player: Player) {
public fun sendToServer(server: String) = BungeeCordRequest(
player,
"Connect",
ByteStreams.newDataOutput().apply{writeUTF(server)}.toByteArray()
).send()
public fun retrieveIp(callback: (address: String, port: Int) -> Unit) = BungeeCordRequest(player, "IP") {
val input = ByteStreams.newDataInput(it)
val ip = input.readUTF()
val port = input.readInt()
callback(ip, port)
}.send()
public suspend fun retrieveIp(): Pair<String, Int> = suspendCoroutine { continuation ->
retrieveIp { address, port -> continuation.resume(address to port) }
}
public fun onlinePlayerAt(server: String = "ALL", callback: (playerCount: Int) -> Unit)
= BungeeCordRequest(player, "PlayerCount", ByteStreams.newDataOutput().apply{writeUTF(server)}.toByteArray()) {
val input = ByteStreams.newDataInput(it)
val serverSendTo = input.readUTF()
val playerCount = input.readInt()
callback(playerCount)
}.send()
public suspend fun onlinePlayerAt(server: String = "ALL"): Int = suspendCoroutine { continuation ->
onlinePlayerAt { continuation.resume(it) }
}
public fun retrieveServerName(callback: (server: String) -> Unit) = BungeeCordRequest(player, "GetServer") {
val input = ByteStreams.newDataInput(it)
val server = input.readUTF()
callback(server)
}.send()
public suspend fun retrieveServerName(): String = suspendCoroutine { continuation ->
retrieveServerName { continuation.resume(it) }
}
public fun kickPlayer(player: String, reason: String) = BungeeCordRequest(this.player, "KickPlayer",
ByteStreams.newDataOutput().apply{writeUTF(player);writeUTF(reason)}.toByteArray()).send()
}
+42
View File
@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+116
View File
@@ -0,0 +1,116 @@
plugins {
id("java")
kotlin("jvm")
kotlin("plugin.serialization") version "1.8.21"
id("com.github.johnrengelman.shadow") version "7.1.2"
id("maven-publish")
id("signing")
}
group = "tech.carcadex"
val globalArtifactId = "kotlinbukkitkit-utility"
val globalName = "kotlinbukkitkit-utility"
repositories {
mavenCentral()
maven {
name = "spigotmc-repo"
url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
}
}
dependencies {
compileOnly("org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT")
api(project(":architecture"))
api(project(":extensions"))
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(8)
}
kotlin {
sourceSets.main {
kotlin.srcDir("build/generated/ksp/main/kotlin")
}
sourceSets.test {
kotlin.srcDir("build/generated/ksp/test/kotlin")
}
}
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = globalArtifactId
groupId = "tech.carcadex"
version = version
from(components["java"])
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
pom {
artifactId = globalArtifactId
groupId = "tech.carcadex"
version = version
name.set(globalArtifactId)
description.set(globalArtifactId)
url.set("https://github.com/CarcadeX/KotlinBukkitKit")
licenses {
license {
name.set("MIT")
url.set("https://mit-license.org/")
}
}
developers {
developer {
id.set("itzRedTea")
name.set("Red Tea")
email.set("red__tea@outlook.com")
}
}
scm {
connection.set("scm:https://github.com/CarcadeX/$globalName.git")
developerConnection.set("git@github.com:CarcadeX/$globalName.git")
url.set("https://github.com/CarcadeX/KotlinBukkitKit")
}
}
}
}
repositories {
maven {
val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
credentials {
username = "itzRedTea"
password = project.findProperty("password").toString()
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications.getByName("maven"))
}
@@ -0,0 +1,352 @@
package tech.carcadex.kotlinbukkitkit.utility.collections
import tech.carcadex.kotlinbukkitkit.architecture.extensions.WithPlugin
import tech.carcadex.kotlinbukkitkit.extensions.scheduler
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
public typealias OnExpireCallback<T> = (T) -> Unit
public fun <E> Plugin.expirationListOf(): ExpirationList<E> = ExpirationListImpl(this)
public fun <E> WithPlugin<*>.expirationListOf(): ExpirationList<E> = plugin.expirationListOf<E>()
public fun <E> expirationListOf(expireTime: Int, plugin: Plugin, vararg elements: E): ExpirationList<E> = plugin.expirationListOf<E>().apply { elements.forEach { add(it, expireTime) } }
public fun <E> Plugin.expirationListOf(expireTime: Int, vararg elements: E): ExpirationList<E> = expirationListOf(expireTime, this, elements = *elements)
public fun <E> WithPlugin<*>.expirationListOf(expireTime: Int, vararg elements: E): ExpirationList<E> = plugin.expirationListOf(expireTime, *elements)
public fun <E> expirationListOf(expireTime: Int, plugin: Plugin, vararg elements: Pair<E, OnExpireCallback<E>>): ExpirationList<E> = plugin.expirationListOf<E>().apply { elements.forEach { (element, onExpire) -> add(element, expireTime, onExpire) } }
public fun <E> Plugin.expirationListOf(expireTime: Int, vararg elements: Pair<E, OnExpireCallback<E>>): ExpirationList<E> = expirationListOf(expireTime, this, elements = *elements)
public fun <E> WithPlugin<*>.expirationListOf(expireTime: Int, vararg elements: Pair<E, OnExpireCallback<E>>): ExpirationList<E> = plugin.expirationListOf(expireTime, *elements)
public interface ExpirationList<E> : MutableIterable<E> {
/**
* Returns the count of elements in this list.
*/
public val size: Int
/**
* Returns `true` if the list contains no elements, `false` otherwise.
*/
public fun isEmpty(): Boolean
/**
* Returns the time missing to expire or `null` if the list don't contain the element
*/
public fun missingTime(element: E): Int?
/**
* Checks if the specified element is contained in this list.
*/
public operator fun contains(element: E): Boolean
/**
* Returns the element at the specified index in the list.
*/
public operator fun get(index: Int): E?
/**
* Returns the index of the first occurrence of the e element in the list, or -1 if the specified
* element is not contained in the list.
*/
public fun indexOf(element: E): Int
/**
* Return the first element in the list or `null` if the list is empty
*/
public fun first(): E?
/**
* Return the last element in the list or `null` if the list is empty
*/
public fun last(): E?
/**
* Removes all elements from this list.
*/
public fun clear()
/**
* Add the element to the list with an expiration time.
*
* [expireTime] in seconds
* callback [onExpire] is called when the element expires.
*/
public fun add(element: E, expireTime: Int, onExpire: OnExpireCallback<E>? = null)
/**
* Add the element in the start of list with an expiration time.
*
* [expireTime] in seconds
* callback [onExpire] is called when the element expires.
*/
public fun addFirst(element: E, expireTime: Int, onExpire: OnExpireCallback<E>? = null)
/**
* Removes an element at the specified [index] from the list.
*
* @return the element that has been removed or `null` if
* the element is not contained in the list.
*/
public fun removeAt(index: Int): E?
/**
* Removes an element from the list
*
* @return `true` if the element was removed.
*/
public fun remove(element: E): Boolean
/**
* Removes the first element from the list.
*
* @return the element if was removed.
*/
public fun removeFirst(): E?
/**
* Removes the last element from the list.
*
* @return the element if was removed.
*/
public fun removeLast(): E?
}
private class ExpirationNode<E>(var element: E, val expireTime: Int) {
var next: ExpirationNode<E>? = null
var previous: ExpirationNode<E>? = null
var onExpire: OnExpireCallback<E>? = null
val startTime: Long = System.currentTimeMillis()
}
public class ExpirationListImpl<E>(private val plugin: Plugin) : ExpirationList<E> {
private var head: ExpirationNode<E>? = null
private var tail: ExpirationNode<E>? = null
private var task: BukkitTask? = null
private var emptyCount: Byte = 0
override val size: Int get() = _size
private var _size: Int = 0
private set
override fun isEmpty(): Boolean = _size == 0
override fun missingTime(element: E): Int? {
return getNodeByElement(element)
?.let { it.expireTime - ((System.currentTimeMillis() - it.startTime) / 1000) }
?.toInt()
}
override operator fun contains(element: E): Boolean = indexOf(element) > -1
override operator fun get(index: Int): E? {
return getNode(index)?.element
}
override fun first(): E? = head?.element
override fun last(): E? = tail?.element
override operator fun iterator(): MutableIterator<E> {
return object : MutableIterator<E> {
private val nodeIterator = nodeIterator()
override fun hasNext() = nodeIterator.hasNext()
override fun next() = nodeIterator.next().element
override fun remove() = nodeIterator.remove()
}
}
override fun indexOf(element: E): Int {
if (head == null) return -1
var node = head
var count = 0
do {
if (node?.element === element) {
return count
}
node = node?.next
count++
} while (node != null)
return -1
}
override fun clear() {
head = null
tail = null
_size = 0
}
override fun add(element: E, expireTime: Int, onExpire: OnExpireCallback<E>?) {
if (expireTime <= 0) throw IllegalArgumentException("expireTime need to be greater then 0")
val newNode = ExpirationNode(element, expireTime).also { it.onExpire = onExpire }
if (head == null) {
head = newNode
tail = newNode
} else {
tail?.next = newNode
tail = newNode
}
_size++
generateTask()
}
override fun addFirst(element: E, expireTime: Int, onExpire: OnExpireCallback<E>?) {
if (expireTime <= 0) throw IllegalArgumentException("expireTime need to be greater then 0")
val newNode = ExpirationNode(element, expireTime).also { it.onExpire = onExpire }
if (head == null) {
head = newNode
tail = newNode
} else {
head?.previous = newNode
head = newNode
}
_size++
generateTask()
}
override fun removeAt(index: Int): E? {
return getNode(index)?.also {
removeNode(it)
}?.element
}
override fun remove(element: E): Boolean = getNodeByElement(element)?.let { true.apply { removeNode(it) } } ?: false
override fun removeFirst(): E? {
val next = head?.next
val headElement = head?.element
if (next == null) {
tail = null
head = null
} else {
head = next
next.previous = null
}
return headElement
}
override fun removeLast(): E? {
val previous = tail?.previous
val tailElement = tail?.element
if (previous == null) {
tail = null
head = null
} else {
tail = previous
previous.next = null
}
return tailElement
}
private fun getNode(index: Int): ExpirationNode<E>? {
if (index < 0 || index >= _size) return null
val mid = _size / 2
return if (index > mid) {
getFromSpecificSide(index - mid, tail) { it?.previous }
} else {
getFromSpecificSide(index, head) { it?.next }
}
}
private fun getNodeByElement(element: E): ExpirationNode<E>? {
if (head == null) return null
var node = head
do {
if (node?.element === element) {
return node
}
node = node?.next
} while (node != null)
return null
}
private inline fun getFromSpecificSide(
count: Int,
start: ExpirationNode<E>?,
next: (ExpirationNode<E>?) -> ExpirationNode<E>?,
): ExpirationNode<E> {
var index = 0
var current = start
while (index != count) {
current = next(current)
index++
}
return current!!
}
private fun nodeIterator(): MutableIterator<ExpirationNode<E>> {
return object : MutableIterator<ExpirationNode<E>> {
private var current = head
override fun hasNext(): Boolean {
return current != null
}
override fun next(): ExpirationNode<E> {
val aux = current
current = current?.next
return aux!!
}
override fun remove() {
if (current != null) {
removeNode(current!!)
}
}
}
}
private fun checkTime(current: Long, node: ExpirationNode<E>) = ((current - node.startTime) / 1000) - node.expireTime >= 0
private fun generateTask() {
if (task == null) {
task = scheduler {
if (isEmpty()) {
emptyCount++
} else {
emptyCount = 0
val current = System.currentTimeMillis()
for (node in nodeIterator()) {
if (checkTime(current, node)) {
if (node.onExpire != null) node.onExpire?.invoke(node.element)
removeNode(node)
}
}
}
if (emptyCount > 9) {
cancel()
task = null
}
}.runTaskTimer(plugin, 0L, 20L)
}
}
private fun removeNode(node: ExpirationNode<E>) {
if (node === head && node === tail) {
head = null
tail = null
} else if (node === head) {
head = node.next?.apply { previous = null }
} else if (node === tail) {
tail = node.previous?.apply { next = null }
} else {
node.apply {
previous?.next = next
next?.previous = previous
}
}
_size--
}
}
@@ -0,0 +1,175 @@
package tech.carcadex.kotlinbukkitkit.utility.collections
import tech.carcadex.kotlinbukkitkit.architecture.extensions.WithPlugin
import tech.carcadex.kotlinbukkitkit.extensions.scheduler
import tech.carcadex.kotlinbukkitkit.utility.extensions.now
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
import java.util.*
public typealias OnExpireMapCallback<K, V> = (K, V) -> Unit
public fun <K, V> Plugin.expirationMapOf(): ExpirationMap<K, V> = ExpirationMapImpl(this)
public fun <K, V> WithPlugin<*>.expirationMapOf(): ExpirationMap<K, V> = plugin.expirationMapOf<K, V>()
public fun <K, V> expirationMapOf(expireTime: Long, plugin: Plugin, vararg elements: Pair<K, V>): ExpirationMap<K, V> = plugin.expirationMapOf<K, V>().apply { elements.forEach { (key, value) -> put(key, value, expireTime) } }
public fun <K, V> Plugin.expirationMapOf(expireTime: Long, vararg elements: Pair<K, V>): ExpirationMap<K, V> = expirationMapOf(expireTime, this, elements = *elements)
public fun <K, V> WithPlugin<*>.expirationMapOf(expireTime: Long, vararg elements: Pair<K, V>): ExpirationMap<K, V> = plugin.expirationMapOf(expireTime, *elements)
public fun <K, V> expirationMapOf(expireTime: Long, plugin: Plugin, vararg elements: Triple<K, V, OnExpireMapCallback<K, V>>): ExpirationMap<K, V> = plugin.expirationMapOf<K, V>().apply { elements.forEach { (key, value, onExpire) -> put(key, value, expireTime, onExpire) } }
public fun <K, V> Plugin.expirationMapOf(expireTime: Long, vararg elements: Triple<K, V, OnExpireMapCallback<K, V>>): ExpirationMap<K, V> = expirationMapOf(expireTime, this, elements = *elements)
public fun <K, V> WithPlugin<*>.expirationMapOf(expireTime: Long, vararg elements: Triple<K, V, OnExpireMapCallback<K, V>>): ExpirationMap<K, V> = plugin.expirationMapOf(expireTime, *elements)
public interface ExpirationMap<K, V> : MutableMap<K, V>, WithPlugin<Plugin> {
/**
* Returns the missing time on seconds to expire the key,
* -1 if was not specified the expiration time before(permanent key) or
* `null` if the key is not contained in the map.
*/
public fun missingTime(key: K): Long?
/**
* Set expiration time to the key and returns `true` if the key is found
* or false if the key is not contained in the map.
*/
public fun expire(key: K, time: Long): Boolean
/**
* Set expiration time to the key and returns `true` if the key is found
* or false if the key is not contained in the map.
*
* [time] in seconds.
* [callback] is called when the key expires.
*/
public fun expire(key: K, time: Long, callback: OnExpireMapCallback<K, V>): Boolean
/**
* Associates the specified [value] with the specified [key] in the map
* with an expiration time.
*
* @return the previous value associated with the key, or `null` if the key was not present in the map.
*/
public fun put(key: K, value: V, time: Long): V?
/**
* Associates the specified [value] with the specified [key] in the map
* with an expiration time.
*
* [time] in seconds.
* [callback] is called when the key expires.
*
* @return the previous value associated with the key, or `null` if the key was not present in the map.
*/
public fun put(key: K, value: V, time: Long, callback: OnExpireMapCallback<K, V>): V?
}
public class ExpirationMapImpl<K, V>(
override val plugin: Plugin,
private val initialMap: MutableMap<K, V> = WeakHashMap(),
) : ExpirationMap<K, V>, MutableMap<K, V> by initialMap {
private val putTime: MutableMap<K, Long> = mutableMapOf()
private val expiration: MutableMap<K, Long> = mutableMapOf()
private val whenExpire: MutableMap<K, OnExpireMapCallback<K, V>> = mutableMapOf()
override fun missingTime(key: K): Long? {
return if (containsKey(key)) {
(expiration.get(key) ?: return -1) - ((now() - putTime.getOrPut(key) { now() }) / 1000)
} else {
null
}
}
private fun ex(key: K, time: Long) {
putTime.put(key, now())
expiration.put(key, time)
}
private fun whenEx(key: K, callback: OnExpireMapCallback<K, V>) {
whenExpire.put(key, callback)
}
override fun expire(key: K, time: Long): Boolean {
if (containsKey(key)) {
ex(key, time)
return true
} else {
return false
}
}
override fun expire(key: K, time: Long, callback: OnExpireMapCallback<K, V>): Boolean {
if (expire(key, time)) {
whenEx(key, callback)
return true
} else {
return false
}
}
override fun put(key: K, value: V): V? {
val result = initialMap.put(key, value)
generateTask()
return result
}
override fun put(key: K, value: V, time: Long): V? {
if (time <= 0) throw IllegalArgumentException("expiration time can't be negative or zero")
val result = put(key, value)
ex(key, time)
return result
}
override fun put(key: K, value: V, time: Long, callback: OnExpireMapCallback<K, V>): V? {
if (time <= 0) throw IllegalArgumentException("expiration time can't be negative or zero")
val result = put(key, value)
ex(key, time)
whenEx(key, callback)
return result
}
override fun remove(key: K): V? {
val result = initialMap.remove(key)
if (result != null) {
putTime.remove(key)
expiration.remove(key)
whenExpire.remove(key)
}
return result
}
private fun checkTime(current: Long, key: K) = ((current - (putTime.getOrPut(key) { current })) / 1000) - (expiration.get(key) ?: 0) >= 0
private var task: BukkitTask? = null
private var emptyCount: Byte = 0
private fun generateTask() {
if (task == null) {
task = scheduler {
if (isEmpty()) {
emptyCount++
} else {
emptyCount = 0
val current = now()
for ((key, value) in entries) {
if (checkTime(current, key)) {
whenExpire.remove(key)?.invoke(key, value)
initialMap.remove(key)
putTime.remove(key)
expiration.remove(key)
}
}
}
if (emptyCount > 5) {
cancel()
task = null
}
}.runTaskTimer(plugin, 0L, 20L)
}
}
}
@@ -0,0 +1,238 @@
package tech.carcadex.kotlinbukkitkit.utility.collections
public typealias ObservableListener = (ObservableAction) -> Unit
public enum class ObservableAction { ADD, SET, REPLACE, REMOVE, CLEAR }
public fun <T> observableListOf(mutableList: MutableList<T>): ObservableList<T> = mutableList.asObservable()
public fun <T> observableSetOf(mutableSet: MutableSet<T>): ObservableSet<T> = mutableSet.asObservable()
public fun <K, V> observableMapOf(mutableMap: MutableMap<K, V>): ObservableMap<K, V> = mutableMap.asObservable()
public fun <T> MutableList<T>.asObservable(): ObservableList<T> = ObservableList(this)
public fun <T> MutableSet<T>.asObservable(): ObservableSet<T> = ObservableSet(this)
public fun <K, V> MutableMap<K, V>.asObservable(): ObservableMap<K, V> = ObservableMap(this)
public class ObservableList<T>(
private val list: MutableList<T>,
) : MutableList<T> by list, ObservableCollection<T> {
override val listeners: MutableList<ObservableListener> = mutableListOf<ObservableListener>()
override fun add(element: T): Boolean {
runListeners(ObservableAction.ADD)
return list.add(element)
}
override fun add(index: Int, element: T) {
runListeners(ObservableAction.ADD)
return list.add(index, element)
}
override fun addAll(index: Int, elements: Collection<T>): Boolean {
runListeners(ObservableAction.ADD)
return list.addAll(index, elements)
}
override fun addAll(elements: Collection<T>): Boolean {
runListeners(ObservableAction.ADD)
return list.addAll(elements)
}
override fun clear() {
list.clear()
runListeners(ObservableAction.CLEAR)
}
override fun listIterator(): MutableListIterator<T> {
return ObservableMutableListIterator(
list.listIterator(),
::runListeners,
)
}
override fun listIterator(index: Int): MutableListIterator<T> {
return ObservableMutableListIterator(
list.listIterator(index),
::runListeners,
)
}
override fun remove(element: T): Boolean {
return list.remove(element).ifTrue {
runListeners(ObservableAction.REMOVE)
}
}
override fun removeAll(elements: Collection<T>): Boolean {
return list.removeAll(elements).ifTrue {
runListeners(ObservableAction.REMOVE)
}
}
override fun removeAt(index: Int): T {
return list.removeAt(index).apply {
runListeners(ObservableAction.REMOVE)
}
}
override fun retainAll(elements: Collection<T>): Boolean {
return list.retainAll(elements).ifTrue {
runListeners(ObservableAction.REMOVE)
}
}
override fun set(index: Int, element: T): T {
return list.set(index, element).apply {
runListeners(ObservableAction.SET)
}
}
}
public class ObservableMutableListIterator<T>(
private val iterator: MutableListIterator<T>,
private val runListeners: ObservableListener,
) : MutableListIterator<T> by iterator {
override fun add(element: T) {
iterator.add(element)
runListeners(ObservableAction.ADD)
}
override fun remove() {
iterator.remove()
runListeners(ObservableAction.REMOVE)
}
override fun set(element: T) {
iterator.set(element)
runListeners(ObservableAction.SET)
}
}
public class ObservableSet<T>(
private val set: MutableSet<T>,
) : MutableSet<T> by set, ObservableCollection<T> {
override val listeners: MutableList<ObservableListener> = mutableListOf<ObservableListener>()
override fun add(element: T): Boolean {
return set.add(element).ifTrue {
runListeners(ObservableAction.ADD)
}
}
override fun addAll(elements: Collection<T>): Boolean {
return set.addAll(elements).ifTrue {
runListeners(ObservableAction.ADD)
}
}
override fun clear() {
set.clear()
runListeners(ObservableAction.CLEAR)
}
override fun iterator(): MutableIterator<T> {
return ObservableMutableIterator(
set.iterator(),
::runListeners,
)
}
override fun remove(element: T): Boolean {
return set.remove(element).ifTrue {
runListeners(ObservableAction.REMOVE)
}
}
override fun removeAll(elements: Collection<T>): Boolean {
return set.removeAll(elements).ifTrue {
runListeners(ObservableAction.REMOVE)
}
}
override fun retainAll(elements: Collection<T>): Boolean {
return set.retainAll(elements).ifTrue {
runListeners(ObservableAction.REMOVE)
}
}
}
public class ObservableMutableIterator<T>(
private val iterator: MutableIterator<T>,
private val runListeners: ObservableListener,
) : MutableIterator<T> by iterator {
override fun remove() {
iterator.remove()
runListeners(ObservableAction.REMOVE)
}
}
public class ObservableMap<K, V>(
private val map: MutableMap<K, V>,
) : MutableMap<K, V> by map, ObservableHolder {
override val listeners: MutableList<ObservableListener> = mutableListOf<ObservableListener>()
override fun clear() {
map.clear()
runListeners(ObservableAction.CLEAR)
}
override fun put(key: K, value: V): V? {
return put(key, value).apply {
runListeners(ObservableAction.ADD)
}
}
override fun putAll(from: Map<out K, V>) {
putAll(from)
runListeners(ObservableAction.ADD)
}
override fun putIfAbsent(key: K, value: V): V? {
return map.putIfAbsent(key, value).also {
if (it == null) runListeners(ObservableAction.ADD)
}
}
override fun remove(key: K): V? {
return map.remove(key)?.also {
runListeners(ObservableAction.REMOVE)
}
}
override fun remove(key: K, value: V): Boolean {
return map.remove(key, value).ifTrue {
runListeners(ObservableAction.REMOVE)
}
}
override fun replace(key: K, first: V, second: V): Boolean {
return map.replace(key, first, second).ifTrue {
runListeners(ObservableAction.REPLACE)
}
}
override fun replace(key: K, value: V): V? {
return map.replace(key, value)?.also {
runListeners(ObservableAction.REPLACE)
}
}
}
public interface ObservableCollection<T> : MutableCollection<T>, ObservableHolder
public interface ObservableHolder {
public val listeners: MutableList<ObservableListener>
public fun observe(listener: ObservableListener) {
listeners.add(listener)
}
public fun runListeners(action: ObservableAction) {
for (listener in listeners) {
listener(action)
}
}
}
private inline fun Boolean.ifTrue(block: () -> Unit) = apply { block() }
@@ -0,0 +1,270 @@
package tech.carcadex.kotlinbukkitkit.utility.collections
import tech.carcadex.kotlinbukkitkit.architecture.extensions.WithPlugin
import tech.carcadex.kotlinbukkitkit.extensions.KListener
import tech.carcadex.kotlinbukkitkit.extensions.event
import tech.carcadex.kotlinbukkitkit.extensions.registerEvents
import tech.carcadex.kotlinbukkitkit.extensions.unregisterListener
import org.bukkit.entity.Player
import org.bukkit.event.player.PlayerKickEvent
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.plugin.Plugin
import java.util.*
import kotlin.collections.HashMap
import kotlin.collections.HashSet
public typealias WhenPlayerQuitCollectionCallback = Player.() -> Unit
public typealias WhenPlayerQuitMapCallback<V> = Player.(V) -> Unit
// List
public fun Plugin.onlinePlayerListOf(): OnlinePlayerList = OnlinePlayerList(this)
public fun WithPlugin<*>.onlinePlayerListOf(): OnlinePlayerList = plugin.onlinePlayerListOf()
public fun onlinePlayerListOf(vararg players: Player, plugin: Plugin): OnlinePlayerList = OnlinePlayerList(plugin).apply { addAll(players) }
public fun Plugin.onlinePlayerListOf(vararg players: Player): OnlinePlayerList = onlinePlayerListOf(*players, plugin = this)
public fun WithPlugin<*>.onlinePlayerListOf(vararg players: Player): OnlinePlayerList = plugin.onlinePlayerListOf(*players)
public fun onlinePlayerListOf(vararg pair: Pair<Player, WhenPlayerQuitCollectionCallback>, plugin: Plugin): OnlinePlayerList = OnlinePlayerList(plugin).apply { pair.forEach { (player, whenPlayerQuit) -> add(player, whenPlayerQuit) } }
public fun Plugin.onlinePlayerListOf(vararg pair: Pair<Player, WhenPlayerQuitCollectionCallback>): OnlinePlayerList = onlinePlayerListOf(*pair, plugin = this)
public fun WithPlugin<*>.onlinePlayerListOf(vararg pair: Pair<Player, WhenPlayerQuitCollectionCallback>): OnlinePlayerList = plugin.onlinePlayerListOf(*pair)
// Set
public fun Plugin.onlinePlayerSetOf(): OnlinePlayerSet = OnlinePlayerSet(this)
public fun WithPlugin<*>.onlinePlayerSetOf(): OnlinePlayerSet = plugin.onlinePlayerSetOf()
public fun onlinePlayerSetOf(vararg players: Player, plugin: Plugin): OnlinePlayerSet = OnlinePlayerSet(plugin).apply { addAll(players) }
public fun Plugin.onlinePlayerSetOf(vararg players: Player): OnlinePlayerSet = onlinePlayerSetOf(*players, plugin = this)
public fun WithPlugin<*>.onlinePlayerSetOf(vararg players: Player): OnlinePlayerSet = plugin.onlinePlayerSetOf(*players)
public fun onlinePlayerSetOf(vararg pair: Pair<Player, WhenPlayerQuitCollectionCallback>, plugin: Plugin): OnlinePlayerSet = OnlinePlayerSet(plugin).apply { pair.forEach { (player, whenPlayerQuit) -> add(player, whenPlayerQuit) } }
public fun Plugin.onlinePlayerSetOf(vararg pair: Pair<Player, WhenPlayerQuitCollectionCallback>): OnlinePlayerSet = onlinePlayerSetOf(*pair, plugin = this)
public fun WithPlugin<*>.onlinePlayerSetOf(vararg pair: Pair<Player, WhenPlayerQuitCollectionCallback>): OnlinePlayerSet = plugin.onlinePlayerSetOf(*pair)
// Map
public fun <V> Plugin.onlinePlayerMapOf(): OnlinePlayerMap<V> = OnlinePlayerMap<V>(this)
public fun <V> WithPlugin<*>.onlinePlayerMapOf(): OnlinePlayerMap<V> = plugin.onlinePlayerMapOf<V>()
public fun <V> onlinePlayerMapOf(vararg pair: Pair<Player, V>, plugin: Plugin): OnlinePlayerMap<V> = OnlinePlayerMap<V>(plugin).apply { putAll(pair) }
public fun <V> Plugin.onlinePlayerMapOf(vararg pair: Pair<Player, V>): OnlinePlayerMap<V> = onlinePlayerMapOf(*pair, plugin = this)
public fun <V> WithPlugin<*>.onlinePlayerMapOf(vararg pair: Pair<Player, V>): OnlinePlayerMap<V> = plugin.onlinePlayerMapOf(*pair)
public fun <V> onlinePlayerMapOf(vararg triple: Triple<Player, V, WhenPlayerQuitMapCallback<V>>, plugin: Plugin): OnlinePlayerMap<V> = OnlinePlayerMap<V>(plugin).apply { triple.forEach { (player, value, whenPlayerQuit) -> put(player, value, whenPlayerQuit) } }
public fun <V> Plugin.onlinePlayerMapOf(vararg triple: Triple<Player, V, WhenPlayerQuitMapCallback<V>>): OnlinePlayerMap<V> = onlinePlayerMapOf(*triple, plugin = this)
public fun <V> WithPlugin<*>.onlinePlayerMapOf(vararg triple: Triple<Player, V, WhenPlayerQuitMapCallback<V>>): OnlinePlayerMap<V> = plugin.onlinePlayerMapOf(*triple)
public class OnlinePlayerList(override val plugin: Plugin) : LinkedList<Player>(), OnlinePlayerCollection {
private val whenQuit: MutableList<Pair<Player, WhenPlayerQuitCollectionCallback>> = LinkedList()
override fun add(player: Player, whenPlayerQuitCallback: Player.() -> Unit): Boolean {
if (super<OnlinePlayerCollection>.add(player, whenPlayerQuitCallback)) {
whenQuit.add(player to whenPlayerQuitCallback)
return true
} else {
return false
}
}
override fun add(element: Player): Boolean {
if (isEmpty()) registerEvents(plugin)
return super<LinkedList>.add(element)
}
override fun quit(player: Player): Boolean {
if (super.quit(player)) {
val iterator = whenQuit.iterator()
for (pair in iterator) {
if (pair.first == player) {
iterator.remove()
pair.second.invoke(pair.first)
}
}
return true
} else {
return false
}
}
override fun removeFirst(): Player {
return super.removeFirst().also {
checkRegistration()
}
}
override fun removeLastOccurrence(p0: Any?): Boolean {
return super.removeLastOccurrence(p0).also {
if (it) checkRegistration()
}
}
override fun removeAt(p0: Int): Player {
return super.removeAt(p0).also {
checkRegistration()
}
}
override fun remove(element: Player): Boolean {
if (super.remove(element)) {
checkRegistration()
return true
} else {
return false
}
}
override fun removeLast(): Player {
return super.removeLast().also {
checkRegistration()
}
}
}
public class OnlinePlayerSet(override val plugin: Plugin) : HashSet<Player>(), OnlinePlayerCollection {
private val whenQuit: MutableMap<Player, WhenPlayerQuitCollectionCallback> = mutableMapOf()
override fun add(player: Player, whenPlayerQuitCallback: WhenPlayerQuitCollectionCallback): Boolean {
if (super<OnlinePlayerCollection>.add(player, whenPlayerQuitCallback)) {
whenQuit.put(player, whenPlayerQuitCallback)
checkRegistration()
return true
} else {
return false
}
}
override fun add(element: Player): Boolean {
return super<HashSet>.add(element).also {
if (it) checkRegistration()
}
}
override fun remove(element: Player): Boolean {
return super.remove(element).also {
if (it) checkRegistration()
}
}
override fun quit(player: Player): Boolean {
if (super.quit(player)) {
whenQuit.remove(player)?.also { block ->
block.invoke(player)
}
return true
} else {
return false
}
}
}
public interface OnlinePlayerCollection : MutableCollection<Player>, KListener<Plugin> {
/**
* Adds a new Player to the collection with a callback for when the player quits the server.
*/
public fun add(player: Player, whenPlayerQuit: WhenPlayerQuitCollectionCallback): Boolean {
return add(player).also {
if (it) checkRegistration()
}
}
/**
* Removes the player from the collection, calling the [WhenPlayerQuitCollectionCallback] provided.
*/
public fun quit(player: Player): Boolean {
return remove(player).also {
if (it) checkRegistration()
}
}
/**
* Clear the collection calling all [WhenPlayerQuitCollectionCallback] from the Players.
*/
public fun clearQuiting() {
toMutableList().forEach {
quit(it)
}
}
}
internal fun OnlinePlayerCollection.checkRegistration() {
if (size == 1) {
event<PlayerQuitEvent> { quit(player) }
event<PlayerKickEvent> { quit(player) }
} else if (size == 0) {
unregisterListener()
}
}
public class OnlinePlayerMap<V>(override val plugin: Plugin) : HashMap<Player, V>(), KListener<Plugin> {
private val whenQuit: HashMap<Player, WhenPlayerQuitMapCallback<V>> = hashMapOf()
/**
* Puts a Player to the map with a [value] and a callback for when the player quits the server.
*/
public fun put(key: Player, value: V, whenPlayerQuit: WhenPlayerQuitMapCallback<V>): V? {
whenQuit.put(key, whenPlayerQuit)
return put(key, value).also {
checkRegistration()
}
}
/**
* Removes the player from the map, calling the [WhenPlayerQuitMapCallback] provided.
*/
public fun quit(player: Player) {
remove(player)?.also {
whenQuit.remove(player)?.also { block ->
block.invoke(player, it)
}
checkRegistration()
}
}
/**
* Clear the map calling all [WhenPlayerQuitMapCallback] from the Players.
*/
public fun clearQuiting() {
keys.toMutableList().forEach {
quit(it)
}
}
override fun remove(key: Player): V? {
return super.remove(key).also {
checkRegistration()
}
}
override fun remove(key: Player, value: V): Boolean {
return super.remove(key, value).also {
checkRegistration()
}
}
private fun checkRegistration() {
if (size == 1) {
event<PlayerQuitEvent> { quit(player) }
event<PlayerKickEvent> { quit(player) }
} else if (size == 0) {
unregisterListener()
}
}
}
@@ -0,0 +1,28 @@
package tech.carcadex.kotlinbukkitkit.utility.extensions
public fun Collection<String>.containsIgnoreCase(
element: String,
): Boolean = any { it.equals(element, true) }
public fun <T> MutableCollection<T>.clear(onRemove: (T) -> Unit) {
toMutableList().forEach {
remove(it)
onRemove(it)
}
}
public fun Array<String>.containsIgnoreCase(
element: String,
): Boolean = any { it.equals(element, true) }
public fun <V> Map<String, V>.containsKeyIgnoreCase(
key: String,
): Boolean = keys.containsIgnoreCase(key)
public fun <V> Map<String, V>.getIgnoreCase(
key: String,
): V? = entries.find { it.key.equals(key, true) }?.value
public fun <K, V> MutableMap<K, V>.clear(onRemove: (K, V) -> Unit) {
keys.toMutableSet().forEach { onRemove(it, remove(it)!!) }
}
@@ -0,0 +1,19 @@
package tech.carcadex.kotlinbukkitkit.utility.extensions
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit
public fun now(): Long = System.currentTimeMillis()
public fun nowNano(): Long = System.nanoTime()
public val Long.ticks: Duration get() = toDouble().ticks
public val Int.ticks: Duration get() = toDouble().ticks
public val Double.ticks: Duration get() = tickToMilliseconds(this).milliseconds
public val Duration.inTicks: Double get() = millisecondsToTick(toDouble(DurationUnit.MILLISECONDS))
public fun Duration.toLongTicks(): Long = inTicks.toLong()
private fun tickToMilliseconds(value: Double): Double = value * 50.0
private fun millisecondsToTick(value: Double): Double = value / 50.0
@@ -0,0 +1,122 @@
package tech.carcadex.kotlinbukkitkit.utility.extensions
import tech.carcadex.kotlinbukkitkit.utility.types.BlockPos
import tech.carcadex.kotlinbukkitkit.utility.types.ChunkPos
import tech.carcadex.kotlinbukkitkit.utility.types.VectorComparable
import tech.carcadex.kotlinbukkitkit.utility.types.asBlockPos
import tech.carcadex.kotlinbukkitkit.utility.types.asBukkitBlock
import tech.carcadex.kotlinbukkitkit.utility.types.asBukkitChunk
import tech.carcadex.kotlinbukkitkit.utility.types.asBukkitLocation
import tech.carcadex.kotlinbukkitkit.utility.types.asPos
import org.bukkit.Chunk
import org.bukkit.Location
import org.bukkit.block.Block
// BUKKIT THINGS
public operator fun PosRange<*, BlockPos>.contains(other: Location): Boolean = contains(other.asPos())
public operator fun PosRange<*, BlockPos>.contains(other: Block): Boolean = contains(other.asPos())
public operator fun PosRange<*, ChunkPos>.contains(other: Chunk): Boolean = contains(other.asPos())
public operator fun Location.rangeTo(other: Location): PosRange<Location, BlockPos> {
return PosRange(this.asBlockPos(), other.asBlockPos()) {
RangeIteratorWithFactor<Location, BlockPos>(
this,
other,
{ it.asBukkitLocation(world) },
{ it.asBlockPos() },
)
}
}
public operator fun Block.rangeTo(other: Block): PosRange<Block, BlockPos> {
return PosRange(this.asPos(), other.asPos()) {
RangeIteratorWithFactor<Block, BlockPos>(
this,
other,
{ it.asBukkitBlock(world) },
{ it.asPos() },
)
}
}
public operator fun Chunk.rangeTo(other: Chunk): PosRange<Chunk, ChunkPos> {
return PosRange(this.asPos(), other.asPos()) {
RangeIteratorWithFactor<Chunk, ChunkPos>(
this,
other,
{ it.asBukkitChunk(world) },
{ it.asPos() },
)
}
}
public class PosRange<T, POS : VectorComparable<POS>>(
public val first: POS,
public val last: POS,
private val buildIterator: () -> Iterator<T>,
) : ClosedRange<POS>, Iterable<T> {
override val endInclusive: POS get() = last
override val start: POS get() = first
override fun contains(value: POS): Boolean {
val firstAxis = first.axis()
val lastAxis = last.axis()
return value.axis().withIndex().all { (index, it) ->
it >= firstAxis[index] && it <= lastAxis[index]
}
}
override fun iterator(): Iterator<T> = buildIterator()
}
public class PosRangeIterator<T : VectorComparable<T>>(
first: T,
last: T,
private val factor: (axis: IntArray) -> T,
) : Iterator<T> {
private val firstAxis = first.axis()
private val lastAxis = last.axis()
private val closedAxisRanges = firstAxis.mapIndexed { index, it ->
IntProgression.fromClosedRange(it.toInt(), lastAxis[index].toInt(), 1)
}
private val iteratorAxis = closedAxisRanges.map { it.iterator() }.toTypedArray()
private val actualAxis = iteratorAxis.toList().subList(0, iteratorAxis.size - 1)
.map { it.nextInt() }
.toTypedArray()
override fun hasNext(): Boolean {
return iteratorAxis.any { it.hasNext() }
}
override fun next(): T {
val lastIndex = iteratorAxis.size - 1
val last = iteratorAxis[lastIndex]
if (last.hasNext()) {
val axis = IntArray(actualAxis.size) { actualAxis[it] } + last.nextInt()
return factor(axis)
}
for (i in lastIndex - 1 downTo 0) {
val axis = iteratorAxis[i]
if (axis.hasNext()) {
actualAxis[i] = axis.nextInt()
iteratorAxis[i + 1] = closedAxisRanges[i + 1].iterator()
break
}
}
return next()
}
}
public class RangeIteratorWithFactor<T, POS : VectorComparable<POS>>(
start: T,
end: T,
private val factor: (POS) -> T,
private val posFactor: (T) -> POS,
) : Iterator<T> {
public val iterator: PosRangeIterator<POS> = PosRangeIterator(posFactor(start), posFactor(end), posFactor(start)::factor)
override fun hasNext(): Boolean = iterator.hasNext()
override fun next(): T = factor(iterator.next())
}
@@ -0,0 +1,19 @@
package tech.carcadex.kotlinbukkitkit.utility.extensions
import kotlin.random.Random
public fun <E> Collection<E>.randomOrNull(): E? = runCatching { random() }.getOrNull()
public fun <E> Collection<E>.randomIndex(): Int = if (size > 0) Random.nextInt(size) else -1
public fun <E> Collection<E>.randomize(): MutableList<E> {
val oldList = toMutableList()
val newList = mutableListOf<E>()
for (i in 0 until size) {
val index = oldList.randomIndex()
newList.add(oldList.get(index))
oldList.removeAt(index)
}
return newList
}
@@ -0,0 +1,34 @@
package tech.carcadex.kotlinbukkitkit.utility.extensions
private val unicodeRegex = "((\\\\u)([0-9]{4}))".toRegex()
public fun String.javaUnicodeToCharacter(): String = unicodeRegex.replace(this) {
String(charArrayOf(it.destructured.component3().toInt(16).toChar()))
}
public fun <T> T.print(): T = also { println(it) }
public fun String.centralize(
length: Int,
spacer: String = " ",
prefix: String = "",
suffix: String = "",
): String {
if (this.length >= length) return this
val part = prefix + spacer.repeat((length - this.length) / 2) + suffix
return part + this + part
}
public val TRUE_CASES: Array<String> = arrayOf("true")
get() = field.clone()
public val FALSE_CASES: Array<String> = arrayOf("false")
get() = field.clone()
public fun String.toBooleanOrNull(
trueCases: Array<String> = TRUE_CASES,
falseCases: Array<String> = FALSE_CASES,
): Boolean? = when {
trueCases.any { it.equals(this, true) } -> true
falseCases.any { it.equals(this, true) } -> false
else -> null
}
@@ -0,0 +1,87 @@
package tech.carcadex.kotlinbukkitkit.utility.types
import tech.carcadex.kotlinbukkitkit.utility.extensions.PosRange
import tech.carcadex.kotlinbukkitkit.utility.extensions.PosRangeIterator
import org.bukkit.Chunk
import org.bukkit.Location
import org.bukkit.World
import org.bukkit.block.Block
import kotlin.math.sqrt
public fun blockPosOf(x: Int, y: Int, z: Int): BlockPos = BlockPos(x, y, z)
public fun locationPosOf(x: Double, y: Double, z: Double, yaw: Float = 0f, pitch: Float = 0f): LocationPos = LocationPos(x, y, z, yaw, pitch)
public fun chunkPosOf(x: Int, z: Int): ChunkPos = ChunkPos(x, z)
public fun Location.asPos(): LocationPos = LocationPos(x, y, z, yaw, pitch)
public fun LocationPos.asBukkitBlock(world: World): Block = world.getBlockAt(x.toInt(), y.toInt(), z.toInt())
public fun LocationPos.asBukkitLocation(world: World): Location = Location(world, x, y, z)
public fun LocationPos.asBlockPos(): BlockPos = BlockPos(x.toInt(), y.toInt(), z.toInt())
public fun Block.asPos(): BlockPos = BlockPos(x, y, z)
public fun Location.asBlockPos(): BlockPos = BlockPos(blockX, blockY, blockZ)
public fun BlockPos.asBukkitBlock(world: World): Block = world.getBlockAt(x, y, z)
public fun BlockPos.asBukkitLocation(world: World?): Location = Location(world, x.toDouble(), y.toDouble(), z.toDouble())
public fun BlockPos.asLocationPos(): LocationPos = LocationPos(x.toDouble(), y.toDouble(), z.toDouble(), 0f, 0f)
public fun BlockPos.asChunkPos(): ChunkPos = ChunkPos(x shr 4, z shr 4)
public fun Chunk.asPos(): ChunkPos = ChunkPos(x, z)
public fun ChunkPos.asBukkitChunk(world: World): Chunk = world.getChunkAt(x, z)
public data class BlockPos(
var x: Int,
var y: Int,
var z: Int,
) : VectorComparable<BlockPos> {
override fun axis(): DoubleArray = doubleArrayOf(x.toDouble(), y.toDouble(), z.toDouble())
override fun factor(axis: IntArray): BlockPos = BlockPos(axis[0], axis[1], axis[2])
}
public data class LocationPos(
var x: Double,
var y: Double,
var z: Double,
val yaw: Float,
val pitch: Float,
) : VectorComparable<LocationPos> {
override fun axis(): DoubleArray = doubleArrayOf(x, y, z)
override fun factor(axis: IntArray): LocationPos = LocationPos(axis[0].toDouble(), axis[1].toDouble(), axis[2].toDouble(), yaw, pitch)
}
public data class ChunkPos(
var x: Int,
var z: Int,
) : VectorComparable<ChunkPos> {
override fun axis(): DoubleArray = doubleArrayOf(x.toDouble(), z.toDouble())
override fun factor(axis: IntArray): ChunkPos = ChunkPos(axis[0], axis[1])
}
public interface VectorComparable<T : VectorComparable<T>> : Comparable<T> {
public fun axis(): DoubleArray
public fun factor(axis: IntArray): T
public operator fun rangeTo(other: T): PosRange<T, T> {
return PosRange(this as T, other) { PosRangeIterator(this, other, ::factor) }
}
override fun compareTo(other: T): Int {
val selfAxis = axis()
val otherAxis = other.axis()
val pairAxis = selfAxis.mapIndexed { index, axis -> axis to otherAxis[index] }
val (d1, d2) = calculatePythagoras(*pairAxis.toTypedArray())
return d1.compareTo(d2)
}
}
private fun Pair<Int, Int>.toDouble() = first.toDouble() to second.toDouble()
private fun Pair<Double, Double>.toInt() = first.toInt() to second.toInt()
private fun calculatePythagoras(vararg positions: Pair<Double, Double>): Pair<Double, Double> {
val pow = positions.map { (x1, x2) -> (x1 * x1) to (x2 * x2) }
val x1Sum = pow.sumOf { (x, _) -> x }
val x2Sum = pow.sumOf { (_, x) -> x }
val d1 = sqrt(x1Sum)
val d2 = sqrt(x2Sum)
return d1 to d2
}
@@ -0,0 +1,114 @@
package tech.carcadex.kotlinbukkitkit.utility.types
import kotlinx.serialization.Serializable
public const val DEFAULT_SPACER: Char = ' '
public const val DEFAULT_FORMAT_STYLE: String = "%YEAR %MONTH %WEEK %DAY %HOUR %MIN %SEC"
public val Long.formatter: TimeFormat get() = TimeFormat(this)
public val Int.formatter: TimeFormat get() = TimeFormat(this.toLong())
public val TimeFormat.PTBR: String get() = format(PTBRFormat)
public val TimeFormat.EN: String get() = format(ENFormat)
@Serializable
public data class FormatLang(
val second: String,
val seconds: String,
val minute: String,
val minutes: String,
val hour: String,
val hours: String,
val day: String,
val days: String,
val week: String,
val weeks: String,
val month: String,
val months: String,
val year: String,
val years: String,
)
/**
* @param time in second
*/
@JvmInline
public value class TimeFormat(private val time: Long) {
public fun format(
lang: FormatLang,
formatStyle: String = DEFAULT_FORMAT_STYLE,
formatSpacer: Char = DEFAULT_SPACER,
): String {
val seconds = time % 60
val minutes = time / 60 % 60
val hours = time / 3600 % 24
val days = time / 86400 % 7
val weeks = time / 604800 % 4
val months = time / 2419200 % 12
val years = time / 29030400
var formated = formatStyle.replace("%SEC", if (seconds > 0) "$seconds ${if (seconds > 1) lang.seconds else lang.second}" else "", true)
formated = formated.replace("%MIN", if (minutes > 0) "$minutes ${if (minutes > 1) lang.minutes else lang.minute}" else "", true)
formated = formated.replace("%HOUR", if (hours > 0) "$hours ${if (hours > 1) lang.hours else lang.hour}" else "", true)
formated = formated.replace("%DAY", if (days > 0) "$days ${if (days > 1) lang.days else lang.day}" else "", true)
formated = formated.replace("%WEEK", if (weeks > 0) "$weeks ${if (weeks > 1) lang.weeks else lang.week}" else "", true)
formated = formated.replace("%MONTH", if (months > 0) "$months ${if (months > 1) lang.months else lang.month}" else "", true)
formated = formated.replace("%YEAR", if (years > 0) "$years ${if (years > 1) lang.years else lang.year}" else "", true)
formated = formated.replace("$formatSpacer$formatSpacer", "$formatSpacer")
return formated.trim { it == formatSpacer }
}
}
public val PTBRFormat: FormatLang = FormatLang(
second = "segundo",
seconds = "segundos",
minute = "minuto",
minutes = "minutos",
hour = "hora",
hours = "horas",
day = "dia",
days = "dias",
week = "semana",
weeks = "semanas",
month = "mes",
months = "meses",
year = "ano",
years = "anos",
)
public val ENFormat: FormatLang = FormatLang(
second = "second",
seconds = "seconds",
minute = "minute",
minutes = "minutes",
hour = "hour",
hours = "hours",
day = "day",
days = "days",
week = "week",
weeks = "weeks",
month = "month",
months = "months",
year = "year",
years = "years",
)
@@ -0,0 +1,25 @@
package tech.carcadex.kotlinbukkitkit.utility.utils
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import kotlin.reflect.KClass
public class HashcodeComparator<T> : Comparator<T> {
override fun compare(p0: T, p1: T): Int {
return p0.hashCode().compareTo(p1.hashCode())
}
}
public object PluginComparator : Comparator<Plugin> {
override fun compare(p0: Plugin, p1: Plugin): Int {
return p0.name.compareTo(p1.name)
}
}
public object PlayerComparator : Comparator<Player> {
override fun compare(p0: Player, p1: Player): Int {
return p0.uniqueId.compareTo(p1.uniqueId)
}
}
public val KClassComparator: HashcodeComparator<KClass<*>> = HashcodeComparator<KClass<*>>()