diff --git a/README.md b/README.md
index db1654b..e956efa 100644
--- a/README.md
+++ b/README.md
@@ -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/
+
+
+
+
+## KotlinBukkitKit
+
+
+[](https://github.com/DevSrSouza/KotlinBukkitAPI/stargazers)
+[](https://github.com/DevSrSouza/KotlinBukkitAPI/issues)
+[](https://github.com/DevSrSouza/KotlinBukkitAPI/commit)
+[](https://discord.gg/pyrh4dUPZD)
+[](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 {
+ player.msg("&3Welcome ${player.name}".translateColor())
+ }
+
+ event {
+ 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 {
+ displayName = "&bGem".translateColor()
+ }
+}
+val encbook = item(Material.ENCHANTED_BOOK).meta {
+ 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(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 official plugin page
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 5e2a2ed..5e6f6ad 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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"))
}
\ No newline at end of file
diff --git a/commands/src/main/kotlin/tech/carcadex/kotlinbukkitkit/commands/Test.kt b/commands/src/main/kotlin/tech/carcadex/kotlinbukkitkit/commands/Test.kt
new file mode 100644
index 0000000..c573e32
--- /dev/null
+++ b/commands/src/main/kotlin/tech/carcadex/kotlinbukkitkit/commands/Test.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/exposed/.gitignore b/exposed/.gitignore
new file mode 100644
index 0000000..b63da45
--- /dev/null
+++ b/exposed/.gitignore
@@ -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
\ No newline at end of file
diff --git a/exposed/build.gradle.kts b/exposed/build.gradle.kts
new file mode 100644
index 0000000..e8c007f
--- /dev/null
+++ b/exposed/build.gradle.kts
@@ -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("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"))
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/DatabaseHelper.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/DatabaseHelper.kt
new file mode 100644
index 0000000..03d4f5b
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/DatabaseHelper.kt
@@ -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.")
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/DatabaseType.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/DatabaseType.kt
new file mode 100644
index 0000000..aa2bf1e
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/DatabaseType.kt
@@ -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> =
+ mapOf("h2" to H2::class, "sqlite" to SQLite::class)
+ public val remoteDatabases: Map> =
+ mapOf("mysql" to MySQL::class, "postgresql" to PostgreSQL::class, "sqlserver" to SQLServer::class)
+ public val databases: Map> = fileDatabases + remoteDatabases
+
+ public fun byName(name: String): KClass? = 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()
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Block.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Block.kt
new file mode 100644
index 0000000..1b75d94
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Block.kt
@@ -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): ExposedDelegate = BlockExposedDelegate(column)
+
+@JvmName("blockNullable")
+public fun Entity<*>.block(column: Column): ExposedDelegate = BlockExposedDelegateNullable(column)
+
+public fun Entity<*>.block(
+ worldColumn: Column,
+ xColumn: Column,
+ yColumn: Column,
+ zColumn: Column,
+): ExposedDelegate = BlockMultiColumnExposedDelegate(worldColumn, xColumn, yColumn, zColumn)
+
+@JvmName("blockNullable")
+public fun Entity<*>.nullableBlock(
+ worldColumn: Column,
+ xColumn: Column,
+ yColumn: Column,
+ zColumn: Column,
+): ExposedDelegate = BlockMultiColumnExposedDelegateNullable(worldColumn, xColumn, yColumn, zColumn)
+
+public class BlockExposedDelegate(
+ public val column: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val xColumn: Column,
+ public val yColumn: Column,
+ public val zColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val xColumn: Column,
+ public val yColumn: Column,
+ public val zColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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)
+ }
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/BlockPos.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/BlockPos.kt
new file mode 100644
index 0000000..1331350
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/BlockPos.kt
@@ -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): ExposedDelegate = BlockPosExposedDelegate(column)
+
+@JvmName("blockPosNullable")
+public fun Entity<*>.blockPos(column: Column): ExposedDelegate = BlockPosExposedDelegateNullable(column)
+
+public fun Entity<*>.blockPos(
+ xColumn: Column,
+ yColumn: Column,
+ zColumn: Column,
+): ExposedDelegate = BlockPosMultiColumnExposedDelegate(xColumn, yColumn, zColumn)
+
+@JvmName("blockPosNullable")
+public fun Entity<*>.nullableBlockPos(
+ xColumn: Column,
+ yColumn: Column,
+ zColumn: Column,
+): ExposedDelegate = BlockPosMultiColumnExposedDelegateNullable(xColumn, yColumn, zColumn)
+
+public class BlockPosExposedDelegate(
+ public val column: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val yColumn: Column,
+ public val zColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val yColumn: Column,
+ public val zColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ value: BlockPos?,
+ ) {
+ entity.apply {
+ xColumn.setValue(entity, desc, value?.x)
+ yColumn.setValue(entity, desc, value?.y)
+ zColumn.setValue(entity, desc, value?.z)
+ }
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Cached.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Cached.kt
new file mode 100644
index 0000000..d3b6ca9
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Cached.kt
@@ -0,0 +1,35 @@
+package tech.carcadex.kotlinbukkitkit.exposed.delegate
+
+import org.jetbrains.exposed.dao.Entity
+import kotlin.reflect.KProperty
+
+public fun ExposedDelegate.cached(): ExposedDelegate = CachedExposedDelegate(this)
+
+public class CachedExposedDelegate(
+ private val delegate: ExposedDelegate,
+) : ExposedDelegate {
+ private var cache: T? = null
+ private var isCached: Boolean = false
+
+ override fun > getValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ ): T {
+ if (!isCached) {
+ cache = delegate.getValue(entity, desc)
+ isCached = true
+ }
+
+ return cache!!
+ }
+
+ override fun > setValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ value: T,
+ ) {
+ delegate.setValue(entity, desc, value)
+ cache = value
+ isCached = true
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Chunk.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Chunk.kt
new file mode 100644
index 0000000..627f85e
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Chunk.kt
@@ -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): ExposedDelegate = ChunkExposedDelegate(column)
+
+@JvmName("chunkNullable")
+public fun Entity<*>.chunk(column: Column): ExposedDelegate = ChunkExposedDelegateNullable(column)
+
+public fun Entity<*>.chunk(
+ worldColumn: Column,
+ xColumn: Column,
+ zColumn: Column,
+): ExposedDelegate = ChunkMultiColumnExposedDelegate(worldColumn, xColumn, zColumn)
+
+@JvmName("chunkNullable")
+public fun Entity<*>.chunk(
+ worldColumn: Column,
+ xColumn: Column,
+ zColumn: Column,
+): ExposedDelegate = ChunkMultiColumnExposedDelegateNullable(worldColumn, xColumn, zColumn)
+
+public class ChunkExposedDelegate(
+ public val column: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val xColumn: Column,
+ public val zColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val xColumn: Column,
+ public val zColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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)
+ }
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/ChunkPos.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/ChunkPos.kt
new file mode 100644
index 0000000..089124d
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/ChunkPos.kt
@@ -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): ExposedDelegate = ChunkPosExposedDelegate(column)
+
+@JvmName("chunkPosNullable")
+public fun Entity<*>.chunkPos(column: Column): ExposedDelegate = ChunkPosExposedDelegateNullable(column)
+
+public fun Entity<*>.chunkPos(
+ xColumn: Column,
+ zColumn: Column,
+): ExposedDelegate = ChunkPosMultiColumnExposedDelegate(xColumn, zColumn)
+
+@JvmName("chunkPosNullable")
+public fun Entity<*>.chunkPos(
+ xColumn: Column,
+ zColumn: Column,
+): ExposedDelegate = ChunkPosMultiColumnExposedDelegateNullable(xColumn, zColumn)
+
+public class ChunkPosExposedDelegate(
+ public val column: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ value: ChunkPos,
+ ) {
+ val parsed = value.run { "$x;$z" }
+ entity.apply { column.setValue(this, desc, parsed) }
+ }
+}
+
+public class ChunkPosExposedDelegateNullable(
+ public val column: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ value: ChunkPos?,
+ ) {
+ val parsed = value?.run { "$x;$z" }
+ entity.apply { column.setValue(this, desc, parsed) }
+ }
+}
+
+public class ChunkPosMultiColumnExposedDelegate(
+ public val xColumn: Column,
+ public val zColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val zColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ value: ChunkPos?,
+ ) {
+ entity.apply {
+ xColumn.setValue(entity, desc, value?.x)
+ zColumn.setValue(entity, desc, value?.z)
+ }
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/ExposedDelegate.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/ExposedDelegate.kt
new file mode 100644
index 0000000..d3fec6d
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/ExposedDelegate.kt
@@ -0,0 +1,18 @@
+package tech.carcadex.kotlinbukkitkit.exposed.delegate
+
+import org.jetbrains.exposed.dao.Entity
+import kotlin.reflect.KProperty
+
+public interface ExposedDelegate {
+
+ public operator fun > getValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ ): T
+
+ public operator fun > setValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ value: T,
+ )
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Item.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Item.kt
new file mode 100644
index 0000000..9fc2cbc
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Item.kt
@@ -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): ExposedDelegate = ItemStackExposedDelegate(column)
+
+@JvmName("itemStackNullable")
+public fun Entity<*>.itemStack(column: Column): ExposedDelegate = ItemStackExposedDelegateNullable(column)
+
+public class ItemStackExposedDelegate(
+ public val column: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ ): ItemStack {
+ val blob = entity.run { column.getValue(this, desc) }
+ return toItemStack(blob.bytes)
+ }
+
+ override operator fun > setValue(
+ entity: Entity,
+ 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,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ desc: KProperty<*>,
+ ): ItemStack? {
+ val blob = entity.run { column.getValue(this, desc) }
+ return blob?.bytes?.let { toItemStack(it) }
+ }
+
+ override operator fun > setValue(
+ entity: Entity,
+ 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
+ }
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Location.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Location.kt
new file mode 100644
index 0000000..f25851f
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/Location.kt
@@ -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): ExposedDelegate = LocationExposedDelegate(column)
+
+@JvmName("locationNullable")
+public fun Entity<*>.location(column: Column): ExposedDelegate = LocationExposedDelegateNullable(column)
+
+public fun Entity<*>.location(
+ worldColumn: Column,
+ xColumn: Column,
+ yColumn: Column,
+ zColumn: Column,
+ yawColumn: Column,
+ pitchColumn: Column,
+): ExposedDelegate = LocationMultiColumnExposedDelegate(worldColumn, xColumn, yColumn, zColumn, yawColumn, pitchColumn)
+
+@JvmName("locationNullable")
+public fun Entity<*>.location(
+ worldColumn: Column,
+ xColumn: Column,
+ yColumn: Column,
+ zColumn: Column,
+ yawColumn: Column,
+ pitchColumn: Column,
+): ExposedDelegate = LocationMultiColumnExposedDelegateNullable(worldColumn, xColumn, yColumn, zColumn, yawColumn, pitchColumn)
+
+public class LocationExposedDelegate(
+ public val column: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val xColumn: Column,
+ public val yColumn: Column,
+ public val zColumn: Column,
+ public val yawColumn: Column,
+ public val pitchColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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,
+ public val xColumn: Column,
+ public val yColumn: Column,
+ public val zColumn: Column,
+ public val yawColumn: Column,
+ public val pitchColumn: Column,
+) : ExposedDelegate {
+ override operator fun > getValue(
+ entity: Entity,
+ 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 > setValue(
+ entity: Entity,
+ 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)
+ }
+ }
+}
diff --git a/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/LocationPos.kt b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/LocationPos.kt
new file mode 100644
index 0000000..b4d67ab
--- /dev/null
+++ b/exposed/src/main/kotlin/tech/carcadex/kotlinbukkitkit/exposed/delegate/LocationPos.kt
@@ -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): ExposedDelegate