diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 417efce..3fe62a6 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -14,7 +14,6 @@
-
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 61f9c49..398e69a 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -36,5 +36,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api/src/main/java/org/wlosp/varo/api/APIInstance.java b/api/src/main/java/org/wlosp/varo/api/APIInstance.java
new file mode 100644
index 0000000..28e6c14
--- /dev/null
+++ b/api/src/main/java/org/wlosp/varo/api/APIInstance.java
@@ -0,0 +1,6 @@
+package org.wlosp.varo.api;
+
+public class APIInstance {
+
+ static VaroInterface instance;
+}
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/Varo.kt b/api/src/main/kotlin/org/wlosp/varo/api/Varo.kt
new file mode 100644
index 0000000..e456e23
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/Varo.kt
@@ -0,0 +1,36 @@
+package org.wlosp.varo.api
+
+import org.bukkit.OfflinePlayer
+import org.wlosp.varo.api.entities.VaroPlayer
+import org.wlosp.varo.api.entities.VaroTeam
+
+/**
+ * Converts this [OfflinePlayer] to a [VaroPlayer].
+ *
+ * @see VaroPlayerApi.get
+ */
+fun OfflinePlayer.toVaroPlayer(): VaroPlayer? = VaroPlayerApi[this]
+
+/**
+ * A list of all [VaroPlayer]s.
+ *
+ * @see VaroPlayerApi.players
+ */
+val varoPlayers: List
+ get() = VaroPlayerApi.players
+
+/**
+ * A list of all online [VaroPlayer]s.
+ *
+ * @see VaroPlayerApi.onlinePlayers
+ */
+val onlineVaroPlayers: List
+ get() = VaroPlayerApi.onlinePlayers
+
+/**
+ * A list of all [VaroTeam]s.
+ *
+ * @see VaroTeamApi.teams
+ */
+val varoTeams: List
+ get() = VaroTeamApi.teams
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/VaroApi.kt b/api/src/main/kotlin/org/wlosp/varo/api/VaroApi.kt
new file mode 100644
index 0000000..726664c
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/VaroApi.kt
@@ -0,0 +1,14 @@
+package org.wlosp.varo.api
+
+import kotlin.properties.Delegates
+
+/**
+ * Singleton for Varo API.
+ *
+ * @see VaroInterface for API documentation
+ */
+object VaroApi : VaroInterface by APIInstance.instance
+
+object VaroTeamApi : VaroTeamApiInterface by VaroApi.teams
+
+object VaroPlayerApi : VaroPlayerApiInterface by VaroApi.players
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/VaroInterface.kt b/api/src/main/kotlin/org/wlosp/varo/api/VaroInterface.kt
new file mode 100644
index 0000000..c52a047
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/VaroInterface.kt
@@ -0,0 +1,104 @@
+package org.wlosp.varo.api
+
+import org.bukkit.Bukkit
+import org.bukkit.OfflinePlayer
+import org.bukkit.entity.Player
+import org.wlosp.varo.api.entities.VaroPlayer
+import org.wlosp.varo.api.entities.VaroTeam
+import java.util.*
+
+typealias VaroTeamApiInterface = VaroInterface.TeamApi
+
+typealias VaroPlayerApiInterface = VaroInterface.PlayerApi
+
+/**
+ * API for WLOSP Varo plugin.
+ *
+ * @see VaroApi
+ */
+interface VaroInterface {
+
+ /**
+ * API for Varo teams.
+ *
+ * @see TeamApi
+ */
+ val teams: TeamApi
+
+ /**
+ * API for Varo players.
+ *
+ * @see PlayerApi
+ */
+ val players: PlayerApi
+
+ /**
+ * Teams related API.
+ */
+ interface TeamApi {
+ /**
+ * An unmodifiable list of all [VaroTeam]s.
+ */
+ val teams: List
+
+ /**
+ * Retrieves a [VaroTeam] by its [name] or `null` if it does not exist.
+ */
+ operator fun get(name: String): VaroTeam?
+ }
+
+ /**
+ * Player related API.
+ */
+ interface PlayerApi {
+
+ /**
+ * An unmodifiable list of all [VaroPlayer].
+ */
+ val players: List
+
+ /**
+ * An unmodifiable list of all [VaroPlayer], that are currently online.
+ */
+ val onlinePlayers: List
+
+ /**
+ * Retrieves a [VaroPlayer] by it's [UUID].
+ *
+ * @throws IllegalArgumentException if the player does not participate in the currently running Varo project
+ */
+ operator fun get(uuid: UUID): VaroPlayer?
+
+ /**
+ * Retrieves the [VaroPlayer] corresponding to this [player].
+ *
+ * @throws IllegalArgumentException if the player does not participate in the currently running Varo project
+ * @see get(UUID)
+ */
+ operator fun get(player: OfflinePlayer): VaroPlayer? = get(player.uniqueId)
+
+ /**
+ * This method only exists to tell players who don't know that [Player] extends [OfflinePlayer] to use the normal get method
+ * as they cannot read docs.
+ *
+ * @see get(OfflinePlayer)
+ */
+ @Deprecated(
+ "Player extends OfflinePlayer so just use the normal get method",
+ ReplaceWith("get"),
+ DeprecationLevel.ERROR
+ )
+ fun getByPlayer(player: Player): VaroPlayer? = get(player)
+
+ /**
+ * Finds the [VaroPlayer] corresponding to its [name].
+ *
+ * @throws IllegalArgumentException when the player is not online
+ */
+ operator fun get(name: String): VaroPlayer? {
+ val player = Bukkit.getPlayer(name)
+ requireNotNull(player) { "The player has to be online" }
+ return get(player)
+ }
+ }
+}
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/entities/StrikeAction.kt b/api/src/main/kotlin/org/wlosp/varo/api/entities/StrikeAction.kt
new file mode 100644
index 0000000..a31dbfe
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/entities/StrikeAction.kt
@@ -0,0 +1,7 @@
+package org.wlosp.varo.api.entities
+
+enum class StrikeAction {
+ PUBLISH_COORDINATES,
+ DELETE_INVENTORY_AND_CHEST,
+ BAN
+}
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/entities/VaroPlayer.kt b/api/src/main/kotlin/org/wlosp/varo/api/entities/VaroPlayer.kt
new file mode 100644
index 0000000..7f30eb4
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/entities/VaroPlayer.kt
@@ -0,0 +1,35 @@
+package org.wlosp.varo.api.entities
+
+import org.bukkit.Bukkit
+import org.bukkit.Location
+import org.bukkit.OfflinePlayer
+import org.bukkit.entity.Player
+import java.util.*
+
+/**
+ * Representation of a Player in this Varo.
+ *
+ * @property uuid the [UUID] of the player
+ * @property team the [VaroTeam] the player is in
+ * @property spawnLocation the [Location] at which the player will spawn when Varo starts.
+ */
+interface VaroPlayer {
+ val uuid: UUID
+ val team: VaroTeam
+ val spawnLocation: Location
+ val isAlive: Boolean
+
+ /**
+ * Returns the corresponding [Player] or `null` if the player is not online.
+ *
+ * @see Bukkit.getPlayer
+ */
+ fun toPlayer(): Player? = Bukkit.getPlayer(uuid)
+
+ /**
+ * Returns the corresponding [OfflinePlayer].
+ *
+ * @see Bukkit.getOfflinePlayer
+ */
+ fun toOfflinePlayer(): OfflinePlayer = Bukkit.getOfflinePlayer(uuid)
+}
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/entities/VaroTeam.kt b/api/src/main/kotlin/org/wlosp/varo/api/entities/VaroTeam.kt
new file mode 100644
index 0000000..c2dd3d2
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/entities/VaroTeam.kt
@@ -0,0 +1,21 @@
+package org.wlosp.varo.api.entities
+
+import org.bukkit.ChatColor
+
+/**
+ * Representation of a Varo team.
+ *
+ * @property name the name of the team
+ * @property color the color of the team (used for Discord and Scoreboard)
+ * @property players an unmodifiable list of [VaroPlayer]s which are in this team
+ * @property isAlive whether the team is still alive or not
+ */
+interface VaroTeam {
+ val name: String
+ val color: ChatColor
+
+ val players: List
+
+ val isAlive: Boolean
+ get() = players.any(VaroPlayer::isAlive)
+}
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/events/VaroEndEvent.kt b/api/src/main/kotlin/org/wlosp/varo/api/events/VaroEndEvent.kt
new file mode 100644
index 0000000..ce62fc6
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/events/VaroEndEvent.kt
@@ -0,0 +1,18 @@
+package org.wlosp.varo.api.events
+
+import org.bukkit.event.Event
+import org.bukkit.event.HandlerList
+import org.wlosp.varo.api.entities.VaroTeam
+
+class VaroEndEvent(
+ val winningTeam: VaroTeam
+) : Event() {
+
+ override fun getHandlers(): HandlerList = HANDLERS
+
+ companion object {
+ @JvmStatic
+ @get:JvmName("getHandlersList")
+ val HANDLERS: HandlerList = HandlerList()
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/events/VaroPlayerDiedEvent.kt b/api/src/main/kotlin/org/wlosp/varo/api/events/VaroPlayerDiedEvent.kt
new file mode 100644
index 0000000..5eeb021
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/events/VaroPlayerDiedEvent.kt
@@ -0,0 +1,25 @@
+package org.wlosp.varo.api.events
+
+import org.bukkit.entity.Player
+import org.bukkit.event.Event
+import org.bukkit.event.HandlerList
+import org.wlosp.varo.api.entities.VaroPlayer
+import org.wlosp.varo.api.entities.VaroTeam
+
+class VaroPlayerDiedEvent(
+ val player: Player,
+ val varoPlayer: VaroPlayer
+) : Event() {
+
+ val varoTeam: VaroTeam by lazy { varoPlayer.team }
+
+ override fun getHandlers(): HandlerList {
+ return HANDLERS
+ }
+
+ companion object {
+ @JvmStatic
+ @get:JvmName("getHandlersList")
+ val HANDLERS: HandlerList = HandlerList()
+ }
+}
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/events/VaroStrikeEvent.kt b/api/src/main/kotlin/org/wlosp/varo/api/events/VaroStrikeEvent.kt
new file mode 100644
index 0000000..64b5f1f
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/events/VaroStrikeEvent.kt
@@ -0,0 +1,22 @@
+package org.wlosp.varo.api.events
+
+import org.bukkit.event.Event
+import org.bukkit.event.HandlerList
+import org.wlosp.varo.api.entities.StrikeAction
+import org.wlosp.varo.api.entities.VaroTeam
+
+class VaroStrikeEvent(
+ val team: VaroTeam,
+ val action: StrikeAction?,
+ val reason: String?,
+ val amount: Int
+) : Event() {
+
+ override fun getHandlers(): HandlerList = HANDLERS
+
+ companion object {
+ @JvmStatic
+ @get:JvmName("getHandlersList")
+ val HANDLERS: HandlerList = HandlerList()
+ }
+}
diff --git a/api/src/main/kotlin/org/wlosp/varo/api/events/VaroTeamDiedEvent.kt b/api/src/main/kotlin/org/wlosp/varo/api/events/VaroTeamDiedEvent.kt
new file mode 100644
index 0000000..0afada9
--- /dev/null
+++ b/api/src/main/kotlin/org/wlosp/varo/api/events/VaroTeamDiedEvent.kt
@@ -0,0 +1,28 @@
+package org.wlosp.varo.api.events
+
+import org.bukkit.event.Event
+import org.bukkit.event.HandlerList
+import org.wlosp.varo.api.entities.VaroPlayer
+import org.wlosp.varo.api.entities.VaroTeam
+
+class VaroTeamDiedEvent(
+ val team: VaroTeam,
+ val cause: Cause
+) : Event() {
+ override fun getHandlers(): HandlerList = HANDLERS
+
+ val players: List
+ get() = team.players
+
+
+ enum class Cause {
+ STRIKE,
+ DEATH
+ }
+
+ companion object {
+ @JvmStatic
+ @get:JvmName("getHandlersList")
+ val HANDLERS: HandlerList = HandlerList()
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 938467e..2073d2c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -17,16 +17,30 @@ repositories {
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots")
maven("https://dl.bintray.com/johnnyjayjay/spiglin")
maven("https://repo.byteflux.net/repository/maven-releases/")
+ maven("https://jitpack.io")
+ jcenter()
mavenCentral()
}
dependencies {
+ implementation(project(":api"))
+
compileOnly("org.spigotmc", "spigot-api", "1.16.1-R0.1-SNAPSHOT")
- compileOnly("com.github.johnnyjayjay", "spiglin", "2.0.3")
+ compileOnly("com.github.johnnyjayjay", "spiglin", "develop-SNAPSHOT")
implementation("net.byteflux", "libby-bukkit", "0.0.1")
+ // Database
+ compileOnly("org.jetbrains.exposed", "exposed-core", "0.25.1")
+ compileOnly("org.jetbrains.exposed", "exposed-dao", "0.25.1")
+ compileOnly("org.jetbrains.exposed", "exposed-jdbc", "0.25.1")
+ compileOnly("com.zaxxer", "HikariCP", "3.4.5")
+
+ // HTTP
+ compileOnly("com.squareup.okhttp3", "okhttp", "4.8.0")
+
compileOnly("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8", "1.3.4")
implementation(kotlin("stdlib-jdk8"))
+ implementation(kotlin("reflect"))
}
tasks {
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1141476..9245c59 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,2 +1,2 @@
rootProject.name = "varo"
-
+include("api")
diff --git a/src/main/kotlin/org/wlosp/varo/VaroPlugin.kt b/src/main/kotlin/org/wlosp/varo/VaroPlugin.kt
index 319178e..0fd5673 100644
--- a/src/main/kotlin/org/wlosp/varo/VaroPlugin.kt
+++ b/src/main/kotlin/org/wlosp/varo/VaroPlugin.kt
@@ -2,9 +2,18 @@ package org.wlosp.varo
import net.byteflux.libby.BukkitLibraryManager
import org.bukkit.plugin.java.JavaPlugin
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.api.VaroApiImpl
+import org.wlosp.varo.commands.registerVaroCommand
import org.wlosp.varo.configuration.Config
+import org.wlosp.varo.database.connectToDatabase
import org.wlosp.varo.dependencies.Dependency
import org.wlosp.varo.dependencies.loadDependencies
+import org.wlosp.varo.entities.Stage
+import org.wlosp.varo.entities.VaroStorage
+import org.wlosp.varo.listeners.*
+import org.wlosp.varo.listeners.registerItemBlockListeners
+import org.wlosp.varo.listeners.registerShutdownHandler
import kotlin.properties.Delegates
/**
@@ -12,20 +21,42 @@ import kotlin.properties.Delegates
*/
@Suppress("unused")
class VaroPlugin : JavaPlugin() {
- private var dependencyManager by Delegates.notNull()
- private var config by Delegates.notNull()
+ internal var dependencyManager by Delegates.notNull()
+ internal var varoConfig by Delegates.notNull()
+ internal var stage: Stage by Delegates.observable(Stage.SETUP) { _, _, it ->
+ transaction {
+ VaroStorage.storage.stage = it
+ }
+ }
override fun onEnable() {
dependencyManager = BukkitLibraryManager(this)
- dependencyManager.loadDependencies(Dependency.SPIGLIN, Dependency.COROUTINES)
+ dependencyManager.loadDependencies(
+ Dependency.SPIGLIN,
+ Dependency.COROUTINES,
+ Dependency.EXPOSED,
+ Dependency.OKHTTP
+ )
saveDefaultConfig()
loadConfig()
+
+ connectToDatabase()
+
+ // Events
+ registerDamageListener()
+ registerShutdownHandler()
+ registerItemBlockListeners()
+ registerTeamHandler()
+ registerStartupSequence()
+
+ registerVaroCommand()
+
+ VaroApiImpl()
}
private fun loadConfig() {
- config = Config.fromFileConfiguration(getConfig())
- println(config)
+ varoConfig = Config.fromFileConfiguration(config)
}
}
diff --git a/src/main/kotlin/org/wlosp/varo/api/VaroApiImpl.kt b/src/main/kotlin/org/wlosp/varo/api/VaroApiImpl.kt
new file mode 100644
index 0000000..822316a
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/api/VaroApiImpl.kt
@@ -0,0 +1,41 @@
+package org.wlosp.varo.api
+
+import org.jetbrains.exposed.dao.load
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.api.entities.VaroPlayer
+import org.wlosp.varo.api.entities.VaroTeam
+import org.wlosp.varo.entities.DatabaseVaroPlayer
+import org.wlosp.varo.entities.DatabaseVaroTeam
+import java.util.*
+
+class VaroApiImpl : VaroInterface {
+
+ init {
+ APIInstance.instance = this
+ }
+
+ override val teams: VaroInterface.TeamApi = TeamsApiImpl()
+ override val players: VaroInterface.PlayerApi = PlayersApiImpl()
+
+ private class TeamsApiImpl : VaroInterface.TeamApi {
+ override val teams: List
+ get() = transaction {
+ DatabaseVaroTeam.all().toList()
+ }
+
+ override fun get(name: String): VaroTeam? = transaction {
+ DatabaseVaroTeam.findById(name)?.load(DatabaseVaroTeam::internalPlayers)
+ }
+ }
+
+ private class PlayersApiImpl : VaroInterface.PlayerApi {
+ override val players: List
+ get() = transaction { DatabaseVaroPlayer.all().map { it.load(DatabaseVaroPlayer::team) } }
+ override val onlinePlayers: List
+ get() = players.filterNot { it.toPlayer() == null }
+
+ override fun get(uuid: UUID): VaroPlayer? = transaction {
+ DatabaseVaroPlayer.findById(uuid)
+ }
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/commands/CommandExtensions.kt b/src/main/kotlin/org/wlosp/varo/commands/CommandExtensions.kt
new file mode 100644
index 0000000..8b4acfd
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/commands/CommandExtensions.kt
@@ -0,0 +1,13 @@
+package org.wlosp.varo.commands
+
+import com.github.johnnyjayjay.spiglin.command.CommandContext
+import org.bukkit.ChatColor
+
+fun CommandContext.withPermission(permission: String, command: (CommandContext) -> Boolean): Boolean {
+ if (!sender.hasPermission(permission)) {
+ sender.sendMessage(ChatColor.RED.toString() + "No Permission")
+ return true
+ }
+
+ return command(this)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/commands/VaroCommand.kt b/src/main/kotlin/org/wlosp/varo/commands/VaroCommand.kt
new file mode 100644
index 0000000..75d2251
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/commands/VaroCommand.kt
@@ -0,0 +1,13 @@
+package org.wlosp.varo.commands
+
+import com.github.johnnyjayjay.spiglin.command.command
+import org.wlosp.varo.VaroPlugin
+
+internal fun VaroPlugin.registerVaroCommand() = command("varo") {
+ subCommand("team") {
+ varoTeamCreateCommand(this@registerVaroCommand)
+ varoTeamDeleteCommand()
+ }
+ varoResetCommand()
+ varoStartCommand(this@registerVaroCommand)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/commands/VaroResetCommand.kt b/src/main/kotlin/org/wlosp/varo/commands/VaroResetCommand.kt
new file mode 100644
index 0000000..4991f42
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/commands/VaroResetCommand.kt
@@ -0,0 +1,31 @@
+package org.wlosp.varo.commands
+
+import com.github.johnnyjayjay.spiglin.command.CommandConvention
+import org.bukkit.Bukkit
+import org.jetbrains.exposed.sql.deleteAll
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.entities.VaroPlayers
+import org.wlosp.varo.entities.VaroStorage
+import org.wlosp.varo.entities.VaroTeams
+
+fun CommandConvention.varoResetCommand() {
+ subCommandExecutor("reset") { context ->
+ context.withPermission("varo.reset") {
+ val scoreboard = Bukkit.getScoreboardManager()?.mainScoreboard ?: error("world not loaded")
+ scoreboard.teams.forEach { team ->
+ if (team.name.startsWith("wlosp_varo")) {
+ team.unregister()
+ }
+ }
+
+ transaction {
+ VaroStorage.storage.delete()
+ VaroPlayers.deleteAll()
+ VaroTeams.deleteAll()
+ }
+
+ it.sender.sendMessage("reseated")
+ true
+ }
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/commands/VaroStartCommand.kt b/src/main/kotlin/org/wlosp/varo/commands/VaroStartCommand.kt
new file mode 100644
index 0000000..84603e9
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/commands/VaroStartCommand.kt
@@ -0,0 +1,75 @@
+package org.wlosp.varo.commands
+
+import com.github.johnnyjayjay.spiglin.broadcast
+import com.github.johnnyjayjay.spiglin.command.CommandConvention
+import com.github.johnnyjayjay.spiglin.scheduler.repeat
+import org.bukkit.scheduler.BukkitTask
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.VaroPlugin
+import org.wlosp.varo.entities.DatabaseVaroPlayer
+import org.wlosp.varo.entities.DatabaseVaroTeam
+import org.wlosp.varo.entities.Stage
+
+private var countdownTask: BukkitTask? = null
+
+fun CommandConvention.varoStartCommand(plugin: VaroPlugin) {
+ subCommandExecutor("abort") { context ->
+ context.withPermission("varo.start") {
+ plugin.stage = Stage.SETUP
+ countdownTask?.cancel()
+ true
+ }
+ }
+
+ subCommandExecutor("prepare") { context ->
+ context.withPermission("varo.start") {
+ val teamsCount = transaction {
+ DatabaseVaroTeam.all().count()
+ }
+
+ if (teamsCount < 2) {
+ it.sender.sendMessage("Du braucht min. 2 teams!")
+ return@withPermission true
+ }
+
+ val players = transaction {
+ DatabaseVaroPlayer.all().toList()
+ }
+
+ players.forEach {
+ val player = it.toPlayer()
+ if (player == null) {
+ context.sender.sendMessage("Es sind nicht alle spieler online")
+ return@withPermission true
+ }
+ player.teleport(it.spawnLocation)
+ }
+
+ plugin.stage = Stage.READY
+ true
+ }
+ }
+
+ subCommandExecutor("start") { context ->
+ context.withPermission("varo.start") {
+ if (plugin.stage != Stage.READY) {
+ it.sender.sendMessage("Bitte mache zuerst /varo prepare")
+ return@withPermission true
+ }
+ plugin.stage = Stage.STARTING
+ countdownTask = plugin.repeat(
+ progression = plugin.varoConfig.startCountDown downTo 0,
+ async = true
+ ) { number ->
+ if (number == 0) {
+ broadcast("start")
+ } else if (number % 5 == 0 || number <= 5) {
+ broadcast(number.toString())
+ }
+ }
+ true
+ }
+ plugin.stage = Stage.RUNNING
+ true
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/commands/VaroTeamCreateCommand.kt b/src/main/kotlin/org/wlosp/varo/commands/VaroTeamCreateCommand.kt
new file mode 100644
index 0000000..dd53509
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/commands/VaroTeamCreateCommand.kt
@@ -0,0 +1,78 @@
+package org.wlosp.varo.commands
+
+import com.github.johnnyjayjay.spiglin.command.CommandConvention
+import org.bukkit.Bukkit
+import org.bukkit.conversations.Conversable
+import org.bukkit.conversations.ConversationFactory
+import org.bukkit.conversations.ExactMatchConversationCanceller
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.VaroPlugin
+import org.wlosp.varo.converstaion.StartPrompt
+import org.wlosp.varo.converstaion.color
+import org.wlosp.varo.converstaion.locations
+import org.wlosp.varo.entities.DatabaseVaroPlayer
+import org.wlosp.varo.entities.DatabaseVaroTeam
+
+fun CommandConvention.varoTeamCreateCommand(plugin: VaroPlugin) {
+ subCommandExecutor("create", false) { context ->
+ context.withPermission("varo.team") {
+ val args = it.args
+ if (args.isEmpty()) return@withPermission false
+ val name = args.first()
+
+ val teamFound =
+ transaction { DatabaseVaroTeam.findById(name) != null }
+
+ if (teamFound) {
+ it.sender.sendMessage("Dieses Team existiert bereits")
+ return@withPermission true
+ }
+
+ val conversationFactory = ConversationFactory(plugin)
+ .withFirstPrompt(StartPrompt(name))
+ .withEscapeSequence("abort")
+ .addConversationAbandonedListener { event ->
+ if (event.canceller !is ExactMatchConversationCanceller) {
+ it.sender.sendMessage("Das team wird erstellt!")
+ val team = transaction {
+ DatabaseVaroTeam.new(name) {
+ color = event.context.color
+ }
+ }
+
+ val scoreboard =
+ Bukkit.getScoreboardManager()?.mainScoreboard
+ ?: error("Could not get scoreboard manager")
+ val scoreboardTeam = scoreboard.registerNewTeam("wlosp_varo_$name")
+ scoreboardTeam.color = team.color
+ scoreboardTeam.setAllowFriendlyFire(plugin.varoConfig.friendlyFire)
+
+ transaction {
+ event.context.locations.forEach { (uuid, location) ->
+ val player = Bukkit.getPlayer(uuid)
+ if (player != null) {
+ scoreboardTeam.addEntry(player.name)
+ }
+
+ DatabaseVaroPlayer.new(uuid) {
+ this.team = team
+
+ this.spawnLocationWorld = location.world?.name ?: error("World is not loaded")
+ this.spawnLocationX = location.x
+ this.spawnLocationY = location.y
+ this.spawnLocationZ = location.z
+ this.spawnLocationYaw = location.yaw
+ this.spawnLocationPitch = location.pitch
+ }
+ }
+ }
+
+ it.sender.sendMessage("Das team mit dem Namen $name wurde erstellt")
+ }
+ }
+
+ conversationFactory.buildConversation(it.sender as Conversable).begin()
+ true
+ }
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/commands/VaroTeamDeleteCommand.kt b/src/main/kotlin/org/wlosp/varo/commands/VaroTeamDeleteCommand.kt
new file mode 100644
index 0000000..35816fd
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/commands/VaroTeamDeleteCommand.kt
@@ -0,0 +1,36 @@
+package org.wlosp.varo.commands
+
+import com.github.johnnyjayjay.spiglin.command.CommandConvention
+import org.bukkit.Bukkit
+import org.jetbrains.exposed.sql.deleteWhere
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.entities.DatabaseVaroTeam
+import org.wlosp.varo.entities.VaroPlayers
+import org.wlosp.varo.entities.VaroTeams
+import org.wlosp.varo.util.ScoreboardTeamUtils
+
+fun CommandConvention.varoTeamDeleteCommand() {
+ subCommandExecutor("delete", executor = { context ->
+ context.withPermission("varo.team") {
+ if (it.args.isEmpty()) return@withPermission false
+ val team = transaction {
+ DatabaseVaroTeam.findById(it.args.first())
+ } ?: return@withPermission it.sender.sendMessage("Team not found").run { true }
+
+ transaction {
+ Bukkit.getScoreboardManager()?.mainScoreboard?.getTeam(ScoreboardTeamUtils.formatTeamName(team.name))
+ ?.unregister() ?: error("Attempted to unregister scorboard team while world is not loaded")
+ VaroPlayers.deleteWhere { VaroPlayers.team eq team.id }
+ team.delete()
+ }
+
+ it.sender.sendMessage("gelöscht")
+ return@withPermission true
+ }
+ }, tabCompleter = {
+ transaction {
+ DatabaseVaroTeam.find { VaroTeams.id like it.args.firstOrNull()?.plus("%").toString() }.map { it.name }
+ .toMutableList()
+ }
+ })
+}
diff --git a/src/main/kotlin/org/wlosp/varo/configuration/Config.kt b/src/main/kotlin/org/wlosp/varo/configuration/Config.kt
index 7d02e14..d52636f 100644
--- a/src/main/kotlin/org/wlosp/varo/configuration/Config.kt
+++ b/src/main/kotlin/org/wlosp/varo/configuration/Config.kt
@@ -3,20 +3,25 @@ package org.wlosp.varo.configuration
import org.bukkit.Material
import org.bukkit.configuration.ConfigurationSection
import org.bukkit.configuration.file.FileConfiguration
+import org.wlosp.varo.api.entities.StrikeAction
+import org.wlosp.varo.database.DatabaseType
import java.time.Duration
import java.time.ZoneId
data class Config(
val name: String,
+ val startCountDown: Int,
val gracePeriod: Int,
val enableWhitelist: Boolean,
val friendlyFire: Boolean,
val fishingRod: Boolean,
+ val itemBlockStrategy: ItemBlockStrategy,
val blockedItems: List,
val time: TimeConfig,
val tablist: TablistConfig,
val strikes: Map,
- val locations: Map
+ val locations: Map,
+ val database: DatabaseConfig
) {
data class TimeConfig(val timezone: ZoneId, val startTime: Duration, val shutdownTime: Duration)
@@ -24,25 +29,44 @@ data class Config(
data class VaroCoordinates(val x: Int, val y: Int, val z: Int)
- enum class StrikeAction {
- PUBLISH_COORDINATES,
- DELETE_INVENTORY_AND_CHEST,
- BAN
+ interface DatabaseConfig {
+ val databaseType: DatabaseType
}
+ data class FileDatabaseConfig(override val databaseType: DatabaseType, val file: String) : DatabaseConfig
+
+ data class ServerDatabaseConfig(
+ override val databaseType: DatabaseType,
+ val host: String,
+ val port: Int,
+ val username: String,
+ val database: String,
+ val password: String,
+ val useSSL: Boolean
+ ) :
+ DatabaseConfig
+
enum class VaroLocation {
SPAWN,
NETHER_PORTAL
}
+ enum class ItemBlockStrategy {
+ DELETE,
+ IGNORE
+ }
+
companion object {
fun fromFileConfiguration(configuration: FileConfiguration): Config {
val name = configuration.getString("name") ?: error("Missing name property in config")
val gracePeriod = configuration.getInt("gracePeriod")
+ val startCountDown = configuration.getInt("startCountDown")
val enableWhitelist = configuration.getBoolean("enableWhitelist")
val friendlyFire = configuration.getBoolean("friendlyFire")
val fishingRod = configuration.getBoolean("fishingRod")
+ val itemBlockStrategy = configuration.getString("itemBlockStrategy")?.let { ItemBlockStrategy.valueOf(it) }
+ ?: error("Missing itemBlockStrategy property in config")
val blockedItems = configuration.getStringList("blockedItems").map(Material::valueOf)
val timeNode = configuration.getConfigurationSection("time") ?: error("Missing time property in config")
@@ -82,22 +106,48 @@ data class Config(
)
}
+ val databaseNode =
+ configuration.getConfigurationSection("database") ?: error("Missing config property database")
+ val type = databaseNode.getString("type")?.let { DatabaseType.valueOf(it.toUpperCase()) }
+ ?: error("Missing config property database")
+ val database = when (type) {
+ DatabaseType.SQLITE -> {
+ val file = databaseNode.getString("file") ?: error("Missing config property database.file")
+ FileDatabaseConfig(type, file)
+ }
+ DatabaseType.MYSQL, DatabaseType.POSTGRESQL -> {
+ val host = databaseNode.getString("host") ?: error("Missing config property database.host")
+ val port = databaseNode.getInt("port")
+ val username =
+ databaseNode.getString("username") ?: error("Missing config property database.username")
+ val database =
+ databaseNode.getString("database") ?: error("Missing config property database.database")
+ val password =
+ databaseNode.getString("password") ?: error("Missing config property database.password")
+ val useSSL = databaseNode.getBoolean("useSSL")
+
+ ServerDatabaseConfig(type, host, port, username, database, password, useSSL)
+ }
+ }
+
return Config(
name,
+ startCountDown,
gracePeriod,
enableWhitelist,
friendlyFire,
fishingRod,
+ itemBlockStrategy,
blockedItems,
time,
tablist,
strikes,
- locations
+ locations,
+ database
)
}
private fun parseDuration(input: String): Duration {
- println(input)
val match = PATTERN.matchEntire(input) ?: error("Time has to be in HH:mm format.")
val (_, hour, minute) = match.groupValues
return Duration.ofHours(hour.toLong()).plusMinutes(minute.toLong())
diff --git a/src/main/kotlin/org/wlosp/varo/converstaion/EnumPrompt.kt b/src/main/kotlin/org/wlosp/varo/converstaion/EnumPrompt.kt
new file mode 100644
index 0000000..7ce0415
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/converstaion/EnumPrompt.kt
@@ -0,0 +1,15 @@
+package org.wlosp.varo.converstaion
+
+import org.bukkit.conversations.ConversationContext
+import org.bukkit.conversations.ValidatingPrompt
+import kotlin.reflect.KClass
+
+abstract class EnumPrompt>(private val clazz: KClass) : ValidatingPrompt() {
+
+ private val values = clazz.java.enumConstants
+
+ override fun isInputValid(context: ConversationContext, input: String): Boolean =
+ values.any { (it as Enum<*>).name == input.toUpperCase() }
+
+ protected fun parseEnum(input: String): T = java.lang.Enum.valueOf(clazz.java, input.toUpperCase())
+}
diff --git a/src/main/kotlin/org/wlosp/varo/converstaion/TeamSetupConversation.kt b/src/main/kotlin/org/wlosp/varo/converstaion/TeamSetupConversation.kt
new file mode 100644
index 0000000..9285c93
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/converstaion/TeamSetupConversation.kt
@@ -0,0 +1,122 @@
+package org.wlosp.varo.converstaion
+
+import org.bukkit.ChatColor
+import org.bukkit.Location
+import org.bukkit.conversations.*
+import org.bukkit.entity.Player
+import org.jetbrains.exposed.sql.and
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.entities.DatabaseVaroPlayer
+import org.wlosp.varo.entities.VaroPlayers
+import org.wlosp.varo.util.AccountUtil
+import java.util.*
+import java.util.regex.Pattern
+
+@Suppress("UNCHECKED_CAST") // it works trust me
+internal var ConversationContext.locations: MutableMap
+ get() = allSessionData["locations"] as MutableMap
+ set(value) {
+ allSessionData["locations"] = value
+ }
+
+internal var ConversationContext.color: ChatColor
+ get() = allSessionData["color"] as ChatColor
+ set(value) {
+ allSessionData["color"] = value
+ }
+
+class StartPrompt(private val teamName: String) : MessagePrompt() {
+ override fun getNextPrompt(context: ConversationContext): Prompt? {
+ context.locations = mutableMapOf()
+ return ColorPrompt
+ }
+
+ override fun getPromptText(context: ConversationContext): String = """
+ Du erstellst das team $teamName
+ """.trimIndent()
+}
+
+class PlayerNamePrompt(private val playerIndex: Int = 1) :
+ RegexPrompt(Pattern.compile("[a-zA-Z0-9_]{3,16}")) {
+
+ override fun acceptInput(context: ConversationContext, input: String?): Prompt? {
+ if (input == "-finish") {
+ if (context.locations.size < 2) {
+ return ErrorPrompt("Mindestens 2 spieler in einem team", this)
+ }
+ return null
+ }
+ return super.acceptInput(context, input)
+ }
+
+ override fun acceptValidatedInput(context: ConversationContext, input: String): Prompt? {
+ val uuid = AccountUtil.fetchUUID(input) ?: return ErrorPrompt(getFailedValidationText(context, input), this)
+ if (uuid in context.locations) return ErrorPrompt("Dieser spieler ist bereits in diesem team", this)
+ val foundPlayer = transaction { DatabaseVaroPlayer.findById(uuid) }
+ if (foundPlayer != null) {
+ return ErrorPrompt("Dieser spieler ist bereits in einem team", this)
+ }
+ return SpawnLocationPrompt(playerIndex, uuid = uuid)
+ }
+
+ override fun getPromptText(context: ConversationContext): String {
+ return "Bitte gebe den Namen von Spieler $playerIndex ein."
+ }
+
+ override fun getFailedValidationText(context: ConversationContext, invalidInput: String): String {
+ return "Dieser name war falsch"
+ }
+
+
+}
+
+object ColorPrompt : EnumPrompt(ChatColor::class) {
+
+ override fun acceptValidatedInput(context: ConversationContext, input: String): Prompt? {
+ context.color = parseEnum(input)
+ return PlayerNamePrompt()
+ }
+
+ override fun getPromptText(context: ConversationContext): String = "bitte farbe angeben"
+
+}
+
+class SpawnLocationPrompt(
+ private val playerIndex: Int = 1,
+ private val uuid: UUID
+) :
+ FixedSetPrompt("here") {
+
+ override fun acceptValidatedInput(context: ConversationContext, input: String): Prompt? {
+ return when (input) {
+ "here" -> {
+ val location = (context.forWhom as Player).location
+ if (context.locations.containsValue(location)) {
+ return ErrorPrompt("Diese location wurde bereits verwendet", this)
+ }
+ val foundLocation =
+ transaction {
+ DatabaseVaroPlayer.find { (VaroPlayers.spawnLocationX eq location.x) and (VaroPlayers.spawnLocationY eq location.y) and (VaroPlayers.spawnLocationZ eq location.z) }
+ .firstOrNull()
+ }
+ if (foundLocation != null) {
+ return ErrorPrompt("Diese location wurde bereits verwendet", this)
+ }
+ context.locations[uuid] = location
+ PlayerNamePrompt(playerIndex + 1)
+ }
+ else -> null
+ }
+ }
+
+ override fun getPromptText(context: ConversationContext): String {
+ return """Bitte gehe zur spawn location für user $playerIndex und gebe "here" ein"""
+ }
+}
+
+class ErrorPrompt(private val text: String, private val nextPrompt: Prompt) : MessagePrompt() {
+ override fun getNextPrompt(context: ConversationContext): Prompt? = nextPrompt
+
+ override fun getPromptText(context: ConversationContext): String = text
+
+}
diff --git a/src/main/kotlin/org/wlosp/varo/database/DatabaseController.kt b/src/main/kotlin/org/wlosp/varo/database/DatabaseController.kt
new file mode 100644
index 0000000..16b9067
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/database/DatabaseController.kt
@@ -0,0 +1,59 @@
+package org.wlosp.varo.database
+
+import com.zaxxer.hikari.HikariDataSource
+import org.jetbrains.exposed.sql.Database
+import org.jetbrains.exposed.sql.SchemaUtils
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.VaroPlugin
+import org.wlosp.varo.configuration.Config
+import org.wlosp.varo.dependencies.Dependency
+import org.wlosp.varo.dependencies.loadDependency
+import org.wlosp.varo.entities.*
+import java.nio.file.Files
+
+internal fun VaroPlugin.connectToDatabase() {
+ val databaseConfig = varoConfig.database
+ dependencyManager.loadDependency(databaseConfig.databaseType.driver)
+ if (databaseConfig.databaseType.useHikari) {
+ dependencyManager.loadDependency(Dependency.HIKARI)
+ }
+
+ val jdbcString = when (databaseConfig.databaseType) {
+ DatabaseType.SQLITE -> {
+ val file = dataFolder.toPath().resolve((databaseConfig as Config.FileDatabaseConfig).file)
+ if (!Files.exists(file)) {
+ Files.createFile(file)
+ }
+ "jdbc:sqlite:${file.toUri()}"
+ }
+ DatabaseType.MYSQL -> {
+ val config = databaseConfig as Config.ServerDatabaseConfig
+ "jdbc:mysql://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}}"
+ }
+ DatabaseType.POSTGRESQL -> {
+ val config = databaseConfig as Config.ServerDatabaseConfig
+ "jdbc:postgresql://${config.host}:${config.port}/${config.database}?username=${config.username}&password=${config.password}&ssl=${config.useSSL}"
+ }
+ }
+
+ if (databaseConfig.databaseType.useHikari) {
+ val hikari = HikariDataSource().apply {
+ jdbcUrl = jdbcString
+ }
+ Database.connect(datasource = hikari)
+ } else {
+ Database.connect(jdbcString)
+ }
+
+ transaction {
+ SchemaUtils.createMissingTablesAndColumns(VaroTeams, VaroPlayers, VaroStorageTable)
+
+ if (VaroStorage.all().count() == 0L) {
+ VaroStorage.new(1) {
+ stage = Stage.SETUP
+ }
+ } else {
+ stage = VaroStorage.storage.stage
+ }
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/database/DatabaseType.kt b/src/main/kotlin/org/wlosp/varo/database/DatabaseType.kt
new file mode 100644
index 0000000..7a85786
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/database/DatabaseType.kt
@@ -0,0 +1,10 @@
+package org.wlosp.varo.database
+
+import org.wlosp.varo.dependencies.Dependency
+
+enum class DatabaseType(val useHikari: Boolean, val driver: Dependency) {
+
+ SQLITE(false, Dependency.SQLITE_DRIVER),
+ MYSQL(true, Dependency.MYSQL_DRIVER),
+ POSTGRESQL(true, Dependency.POSTGRESQL_DRIVER)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/dependencies/Dependency.kt b/src/main/kotlin/org/wlosp/varo/dependencies/Dependency.kt
index 63794f2..9dd9eb6 100644
--- a/src/main/kotlin/org/wlosp/varo/dependencies/Dependency.kt
+++ b/src/main/kotlin/org/wlosp/varo/dependencies/Dependency.kt
@@ -4,10 +4,33 @@ enum class Dependency(
val groupId: String,
val artifactId: String,
val version: String,
- val repository: String? = null
+ val repository: String? = null,
+ val dependencies: List = emptyList()
) {
+ OKIO("com.squareup.okio", "okio", "2.7.0"),
+ OKHTTP("com.squareup.okhttp3", "okhttp", "4.8.0", dependencies = listOf(OKIO)),
+
+ SLF4J("org.slf4j", "slf4j-api", "1.7.25"),
+
+ SPIGLIN("com.github.johnnyjayjay", "spiglin", "develop-SNAPSHOT", "https://jitpack.io"),
- SPIGLIN("com.github.johnnyjayjay", "spiglin", "2.0.3", "https://dl.bintray.com/johnnyjayjay/spiglin"),
KORD("com.gitlab.kordlib.kord", "kord-core", "0.5.6"),
- COROUTINES("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8", "1.3.4")
+
+ COROUTINES_CORE("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.3.4"),
+ COROUTINES("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8", "1.3.4", dependencies = listOf(COROUTINES_CORE)),
+
+ SQLITE_DRIVER("org.xerial", "sqlite-jdbc", "3.32.3"),
+ MYSQL_DRIVER("mysql", "mysql-connector.java", "5.1.49"),
+ POSTGRESQL_DRIVER("org.postgresql", "postgresql", "42.2.14"),
+
+ EXPOSED_DAO("org.jetbrains.exposed", "exposed-dao", "0.25.1"),
+ EXPOSED_JDBC("org.jetbrains.exposed", "exposed-jdbc", "0.25.1"),
+ EXPOSED(
+ "org.jetbrains.exposed",
+ "exposed-core",
+ "0.25.1",
+ dependencies = listOf(EXPOSED_DAO, EXPOSED_JDBC, SLF4J)
+ ),
+
+ HIKARI("com.zaxxer", "HikariCP", "3.4.5")
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/dependencies/DependencyLoader.kt b/src/main/kotlin/org/wlosp/varo/dependencies/DependencyLoader.kt
index c067651..a9434bd 100644
--- a/src/main/kotlin/org/wlosp/varo/dependencies/DependencyLoader.kt
+++ b/src/main/kotlin/org/wlosp/varo/dependencies/DependencyLoader.kt
@@ -2,16 +2,21 @@ package org.wlosp.varo.dependencies
import net.byteflux.libby.BukkitLibraryManager
import net.byteflux.libby.Library
+import net.byteflux.libby.LibraryManager
fun BukkitLibraryManager.loadDependencies(vararg dependencies: Dependency) {
- dependencies.forEach { dependency ->
- dependency.repository?.let { addRepository(it) }
- addJCenter()
- val library = Library.builder()
- .groupId(dependency.groupId)
- .artifactId(dependency.artifactId)
- .version(dependency.version)
- .build()
- loadLibrary(library)
- }
-}
\ No newline at end of file
+ addJCenter()
+ addMavenCentral()
+ dependencies.forEach(::loadDependency)
+}
+
+fun LibraryManager.loadDependency(dependency: Dependency) {
+ dependency.dependencies.forEach(::loadDependency)
+ dependency.repository?.let { addRepository(it) }
+ val library = Library.builder()
+ .groupId(dependency.groupId)
+ .artifactId(dependency.artifactId)
+ .version(dependency.version)
+ .build()
+ loadLibrary(library)
+}
diff --git a/src/main/kotlin/org/wlosp/varo/entities/DatabaseVaroPlayer.kt b/src/main/kotlin/org/wlosp/varo/entities/DatabaseVaroPlayer.kt
new file mode 100644
index 0000000..274cf14
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/entities/DatabaseVaroPlayer.kt
@@ -0,0 +1,55 @@
+package org.wlosp.varo.entities
+
+import org.bukkit.Bukkit
+import org.bukkit.Location
+import org.jetbrains.exposed.dao.Entity
+import org.jetbrains.exposed.dao.EntityClass
+import org.jetbrains.exposed.dao.id.EntityID
+import org.jetbrains.exposed.dao.id.IdTable
+import org.jetbrains.exposed.sql.Column
+import org.wlosp.varo.api.entities.VaroPlayer
+import java.util.*
+
+object VaroPlayers : IdTable("varo_players") {
+
+ override val id: Column> = uuid("uuid").entityId()
+ val team: Column> = reference("team_id", VaroTeams)
+ val isAlive: Column = bool("is_alive").default(true)
+ val spawnLocationWorld: Column = text("spawn_location_world")
+ val spawnLocationX: Column = double("spawn_location_x")
+ val spawnLocationY: Column = double("spawn_location_y")
+ val spawnLocationZ: Column = double("spawn_location_z")
+ val spawnLocationYaw: Column = float("spawn_location_yaw")
+ val spawnLocationPitch: Column = float("spawn_location_pitch")
+
+ override val primaryKey: PrimaryKey = PrimaryKey(id)
+
+}
+
+class DatabaseVaroPlayer(id: EntityID) : Entity(id), VaroPlayer {
+ companion object : EntityClass(VaroPlayers)
+
+ override val uuid: UUID
+ get() = id.value
+
+ override var team: DatabaseVaroTeam by DatabaseVaroTeam referencedOn VaroPlayers.team
+ override var isAlive: Boolean by VaroPlayers.isAlive
+
+ var spawnLocationWorld: String by VaroPlayers.spawnLocationWorld
+ var spawnLocationX: Double by VaroPlayers.spawnLocationX
+ var spawnLocationY: Double by VaroPlayers.spawnLocationY
+ var spawnLocationZ: Double by VaroPlayers.spawnLocationZ
+ var spawnLocationYaw: Float by VaroPlayers.spawnLocationYaw
+ var spawnLocationPitch: Float by VaroPlayers.spawnLocationPitch
+
+ override val spawnLocation: Location by lazy {
+ Location(
+ Bukkit.getWorld(spawnLocationWorld),
+ spawnLocationX,
+ spawnLocationY,
+ spawnLocationZ,
+ spawnLocationYaw,
+ spawnLocationPitch
+ )
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/entities/DatabaseVaroTeam.kt b/src/main/kotlin/org/wlosp/varo/entities/DatabaseVaroTeam.kt
new file mode 100644
index 0000000..1a5643d
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/entities/DatabaseVaroTeam.kt
@@ -0,0 +1,32 @@
+package org.wlosp.varo.entities
+
+import org.bukkit.ChatColor
+import org.jetbrains.exposed.dao.Entity
+import org.jetbrains.exposed.dao.EntityClass
+import org.jetbrains.exposed.dao.id.EntityID
+import org.jetbrains.exposed.dao.id.IdTable
+import org.jetbrains.exposed.sql.Column
+import org.jetbrains.exposed.sql.SizedIterable
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.api.entities.VaroPlayer
+import org.wlosp.varo.api.entities.VaroTeam
+
+object VaroTeams : IdTable("teams") {
+ override val id: Column> = text("name").entityId()
+ val color: Column = enumeration("color", ChatColor::class)
+
+ override val primaryKey: PrimaryKey = PrimaryKey(id)
+}
+
+class DatabaseVaroTeam(id: EntityID) : Entity(id), VaroTeam {
+ companion object : EntityClass(VaroTeams)
+
+ override val name: String
+ get() = id.value
+ override var color: ChatColor by VaroTeams.color
+
+ internal val internalPlayers: SizedIterable by DatabaseVaroPlayer referrersOn VaroPlayers.team
+
+ override val players: List
+ get() = internalPlayers.toList()
+}
diff --git a/src/main/kotlin/org/wlosp/varo/entities/VaroStorage.kt b/src/main/kotlin/org/wlosp/varo/entities/VaroStorage.kt
new file mode 100644
index 0000000..2774e89
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/entities/VaroStorage.kt
@@ -0,0 +1,27 @@
+package org.wlosp.varo.entities
+
+import org.jetbrains.exposed.dao.IntEntity
+import org.jetbrains.exposed.dao.IntEntityClass
+import org.jetbrains.exposed.dao.id.EntityID
+import org.jetbrains.exposed.dao.id.IntIdTable
+import org.jetbrains.exposed.sql.Column
+
+object VaroStorageTable : IntIdTable("storage") {
+ val stage: Column = enumeration("stage", Stage::class)
+}
+
+class VaroStorage(id: EntityID) : IntEntity(id) {
+ companion object : IntEntityClass(VaroStorageTable) {
+ val storage: VaroStorage
+ get() = findById(1)!!
+ }
+
+ var stage: Stage by VaroStorageTable.stage
+}
+
+enum class Stage {
+ SETUP,
+ READY,
+ STARTING,
+ RUNNING
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/listeners/DamageListener.kt b/src/main/kotlin/org/wlosp/varo/listeners/DamageListener.kt
new file mode 100644
index 0000000..7b918b4
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/listeners/DamageListener.kt
@@ -0,0 +1,38 @@
+package org.wlosp.varo.listeners
+
+import com.github.johnnyjayjay.spiglin.PluginManager
+import com.github.johnnyjayjay.spiglin.broadcast
+import com.github.johnnyjayjay.spiglin.event.ExtendedListener
+import com.github.johnnyjayjay.spiglin.event.hear
+import org.bukkit.entity.FishHook
+import org.bukkit.entity.Player
+import org.bukkit.event.entity.EntityDamageByEntityEvent
+import org.bukkit.event.entity.EntityDamageEvent
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.VaroPlugin
+import org.wlosp.varo.api.events.VaroPlayerDiedEvent
+import org.wlosp.varo.api.events.VaroTeamDiedEvent
+import org.wlosp.varo.entities.DatabaseVaroPlayer
+
+fun VaroPlugin.registerDamageListener(): ExtendedListener = hear {
+ if (it is EntityDamageByEntityEvent) {
+ it.isCancelled = it.damager is FishHook && varoConfig.fishingRod
+ val entity = it.entity
+ if (entity is Player && entity.health - it.finalDamage <= 0.0) {
+ transaction {
+ val varoPlayer = DatabaseVaroPlayer.findById(entity.uniqueId)
+ if (varoPlayer != null) {
+ PluginManager.callEvent(VaroPlayerDiedEvent(entity, varoPlayer))
+ varoPlayer.isAlive = false
+ if (!varoPlayer.team.isAlive) {
+ PluginManager.callEvent(VaroTeamDiedEvent(varoPlayer.team, VaroTeamDiedEvent.Cause.DEATH))
+ }
+ entity.kickPlayer("Du bist ausgeschieden!")
+ }
+
+ }
+
+ broadcast("${entity.name} ist tot! Cause: ${it.damager}")
+ }
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/listeners/IteamBlocker.kt b/src/main/kotlin/org/wlosp/varo/listeners/IteamBlocker.kt
new file mode 100644
index 0000000..84eddc4
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/listeners/IteamBlocker.kt
@@ -0,0 +1,62 @@
+package org.wlosp.varo.listeners
+
+import com.github.johnnyjayjay.spiglin.event.hear
+import com.github.johnnyjayjay.spiglin.inventory.set
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.block.BlockBreakEvent
+import org.bukkit.event.block.BlockPlaceEvent
+import org.bukkit.event.entity.EntityPickupItemEvent
+import org.bukkit.event.player.PlayerDropItemEvent
+import org.bukkit.event.player.PlayerJoinEvent
+import org.bukkit.inventory.ItemStack
+import org.wlosp.varo.VaroPlugin
+import org.wlosp.varo.configuration.Config
+
+internal fun VaroPlugin.registerItemBlockListeners() {
+ hear {
+ if (!it.player.isAllowedToUseItem(it.itemDrop.itemStack.type, varoConfig.blockedItems)) {
+ it.itemDrop.remove()
+ }
+ }
+
+ hear {
+ if ((it.entity as? Player)?.isAllowedToUseItem(it.item.itemStack.type, varoConfig.blockedItems) == false) {
+ it.isCancelled = true
+ it.item.remove()
+ }
+ }
+
+ hear {
+ if (!it.player.isAllowedToUseItem(it.blockPlaced.type, varoConfig.blockedItems)) {
+ it.isCancelled = true
+ it.player.sanitizeInventory(varoConfig.blockedItems)
+ }
+ }
+
+ hear {
+ if (!it.player.isAllowedToUseItem(it.block.type, varoConfig.blockedItems)) {
+ it.isCancelled = true
+ if (varoConfig.itemBlockStrategy == Config.ItemBlockStrategy.DELETE) {
+ it.block.setType(Material.AIR, false)
+ }
+ }
+ }
+
+ hear {
+ val player = it.player
+ player.sanitizeInventory(varoConfig.blockedItems)
+ player.updateInventory()
+ }
+}
+
+private fun Player.isAllowedToUseItem(type: Material?, blockedItems: List) =
+ hasPermission("varo.bypassItemBlock") || type !in blockedItems
+
+private fun Player.sanitizeInventory(blockedItems: List) {
+ inventory.forEachIndexed { index, itemStack: ItemStack? ->
+ if (isAllowedToUseItem(itemStack?.type, blockedItems)) {
+ inventory[index] = null
+ }
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/listeners/ShutdownHandler.kt b/src/main/kotlin/org/wlosp/varo/listeners/ShutdownHandler.kt
new file mode 100644
index 0000000..4fcc924
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/listeners/ShutdownHandler.kt
@@ -0,0 +1,54 @@
+package org.wlosp.varo.listeners
+
+import com.github.johnnyjayjay.spiglin.event.hear
+import com.github.johnnyjayjay.spiglin.onlinePlayers
+import com.github.johnnyjayjay.spiglin.scheduler.run
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.time.delay
+import org.bukkit.event.player.PlayerLoginEvent
+import org.wlosp.varo.VaroPlugin
+import org.wlosp.varo.util.toHoursPart
+import org.wlosp.varo.util.toMinutesPart
+import java.time.*
+
+internal fun VaroPlugin.registerShutdownHandler() {
+
+ val date = LocalDate.now(varoConfig.time.timezone)
+ val joinDate = with(varoConfig.time.startTime) {
+ OffsetDateTime.of(
+ date,
+ LocalTime.of(toHoursPart(), toMinutesPart()),
+ varoConfig.time.timezone.rules.getOffset(LocalDateTime.now())
+ )
+ }
+
+ val kickDate =
+ with(varoConfig.time.shutdownTime) {
+ OffsetDateTime.of(
+ date,
+ LocalTime.of(toHoursPart(), toMinutesPart()),
+ varoConfig.time.timezone.rules.getOffset(LocalDateTime.now())
+ )
+ }
+
+ GlobalScope.launch {
+ delay(Duration.between(OffsetDateTime.now(), kickDate))
+ onlinePlayers.forEach {
+ if (!it.hasPermission("varo.joinbypass")) {
+ this@registerShutdownHandler.run {
+ it.kickPlayer("pDer server schließt!")
+ }
+ }
+ }
+ }
+
+ hear { event ->
+ val now = OffsetDateTime.ofInstant(Instant.now(), varoConfig.time.timezone)
+ if (now.isBefore(joinDate) or now.isAfter(kickDate)) {
+ if (!event.player.hasPermission("varo.joinbypass")) {
+ event.disallow(PlayerLoginEvent.Result.KICK_OTHER, "Varo server is closed!")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/listeners/StartupSequence.kt b/src/main/kotlin/org/wlosp/varo/listeners/StartupSequence.kt
new file mode 100644
index 0000000..c9f7278
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/listeners/StartupSequence.kt
@@ -0,0 +1,29 @@
+package org.wlosp.varo.listeners
+
+import com.github.johnnyjayjay.spiglin.event.hear
+import org.bukkit.event.block.BlockBreakEvent
+import org.bukkit.event.player.PlayerJoinEvent
+import org.bukkit.event.player.PlayerMoveEvent
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.VaroPlugin
+import org.wlosp.varo.entities.Stage
+import org.wlosp.varo.entities.DatabaseVaroPlayer
+
+fun VaroPlugin.registerStartupSequence() {
+ hear {
+ if (stage == Stage.STARTING) {
+ val varoPlayer = transaction { DatabaseVaroPlayer.findById(it.player.uniqueId) }
+ varoPlayer?.let { player ->
+ it.player.teleport(player.spawnLocation)
+ }
+ }
+ }
+
+ hear {
+ it.isCancelled = (stage == Stage.STARTING || stage == Stage.READY) && !it.player.hasPermission("varo.admin")
+ }
+
+ hear {
+ it.isCancelled = (stage == Stage.STARTING || stage == Stage.READY) && !it.player.hasPermission("varo.admin")
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/listeners/TeamHandler.kt b/src/main/kotlin/org/wlosp/varo/listeners/TeamHandler.kt
new file mode 100644
index 0000000..1d051c0
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/listeners/TeamHandler.kt
@@ -0,0 +1,38 @@
+package org.wlosp.varo.listeners
+
+import com.github.johnnyjayjay.spiglin.event.hear
+import org.bukkit.Bukkit
+import org.bukkit.event.player.PlayerLoginEvent
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.wlosp.varo.VaroPlugin
+import org.wlosp.varo.entities.DatabaseVaroPlayer
+import org.wlosp.varo.util.ScoreboardTeamUtils
+
+fun VaroPlugin.registerTeamHandler() {
+ hear {
+ val varoPlayer = transaction { DatabaseVaroPlayer.findById(it.player.uniqueId) }
+
+ if (varoPlayer == null) {
+ if (!it.player.hasPermission("varo.joinbypass")) {
+ it.disallow(PlayerLoginEvent.Result.KICK_OTHER, "Du nimmst nicht an Varo teil!")
+ }
+ return@hear
+ }
+
+ if (!varoPlayer.isAlive) {
+ if (!it.player.hasPermission("varo.joinbypass")) {
+ it.disallow(PlayerLoginEvent.Result.KICK_OTHER, "Du bist von Varo ausgeschieden!")
+ }
+ return@hear
+ }
+
+ val scoreboardTeamName = transaction { ScoreboardTeamUtils.formatTeamName(varoPlayer.team.name) }
+ val scoreboard = Bukkit.getScoreboardManager()?.mainScoreboard ?: error("Missing scoreboard")
+ val team = scoreboard.getTeam(scoreboardTeamName) ?: error("Missing scoreboard team")
+
+ val playerName = it.player.name
+ if (playerName !in team.entries) {
+ team.addEntry(playerName)
+ }
+ }
+}
diff --git a/src/main/kotlin/org/wlosp/varo/util/AccountUtil.kt b/src/main/kotlin/org/wlosp/varo/util/AccountUtil.kt
new file mode 100644
index 0000000..5a63c4c
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/util/AccountUtil.kt
@@ -0,0 +1,37 @@
+package org.wlosp.varo.util
+
+import com.google.gson.JsonParser
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import java.util.*
+
+object AccountUtil {
+ private val PROFILES_ENDPOINT = "https://api.mojang.com/users/profiles/minecraft".toHttpUrl()
+ private val uuidPattern = "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})".toRegex()
+
+ val httpClient = OkHttpClient()
+ private val jsonParser = JsonParser()
+
+ fun fetchUUID(name: String): UUID? {
+ val request = Request.Builder()
+ .url(PROFILES_ENDPOINT.newBuilder().addPathSegment(name).build())
+ .get()
+ .build()
+
+ val response = httpClient.newCall(request).execute()
+
+ return response.use {
+ if (it.code != 200) return null
+ val json = it.body?.string()?.let { body ->
+ jsonParser.parse(body).asJsonObject
+ } ?: return null
+
+ if (json.has("error")) {
+ null
+ } else {
+ UUID.fromString(json.get("id").asString.replace(uuidPattern, "$1-$2-$3-$4-$5"))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/util/Extensions.kt b/src/main/kotlin/org/wlosp/varo/util/Extensions.kt
new file mode 100644
index 0000000..6c6fdee
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/util/Extensions.kt
@@ -0,0 +1,7 @@
+package org.wlosp.varo.util
+
+import org.bukkit.command.CommandSender
+import org.bukkit.conversations.ConversationContext
+
+val ConversationContext.sender: CommandSender
+ get() = forWhom as CommandSender
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/util/LocationUtils.kt b/src/main/kotlin/org/wlosp/varo/util/LocationUtils.kt
new file mode 100644
index 0000000..2afdac1
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/util/LocationUtils.kt
@@ -0,0 +1,15 @@
+package org.wlosp.varo.util
+
+import org.bukkit.Bukkit
+import org.bukkit.Location
+
+fun Location.toSerializedString() = "$world:$x:$y:$z:$yaw:$pitch"
+
+fun String.toLocation() = with(split(':')) {
+ val (worldName, x, y, z) = this
+ val yaw = getOrNull(3)?.toFloat() ?: 0F
+ val pitch = getOrNull(4)?.toFloat() ?: 0F
+
+ val world = Bukkit.getWorld(worldName)
+ Location(world, x.toDouble(), y.toDouble(), z.toDouble(), yaw, pitch)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/wlosp/varo/util/ScoreboardTeamUtils.kt b/src/main/kotlin/org/wlosp/varo/util/ScoreboardTeamUtils.kt
new file mode 100644
index 0000000..3058819
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/util/ScoreboardTeamUtils.kt
@@ -0,0 +1,7 @@
+package org.wlosp.varo.util
+
+object ScoreboardTeamUtils {
+
+ fun formatTeamName(name: String): String = "wlosp_varo_$name"
+
+}
diff --git a/src/main/kotlin/org/wlosp/varo/util/TimeUtils.kt b/src/main/kotlin/org/wlosp/varo/util/TimeUtils.kt
new file mode 100644
index 0000000..9600b8a
--- /dev/null
+++ b/src/main/kotlin/org/wlosp/varo/util/TimeUtils.kt
@@ -0,0 +1,7 @@
+package org.wlosp.varo.util
+
+import java.time.Duration
+
+fun Duration.toHoursPart(): Int = (toHours() % 24).toInt()
+
+fun Duration.toMinutesPart(): Int = (toMinutes() % 60).toInt()
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index d22ca73..efe75a4 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -2,12 +2,25 @@ name: WLOSPVaro
gracePeriod: 15000
enableWhitelist: true
borderSize: 1
+startCountDown: 15
shrinkBorderOnDeath: 1
friendlyFire: false
fishingRodPvp: true
+itemBlockStrategy: DELETE # or IGNORE
blockedItems:
- STONE
+database:
+ type: SQLITE # SQLITE, MYSQL or POSTGRESQL
+ file: database.db # only needed for SQLITE type
+ # those are only needed for MYSQL and POSTGRESQL
+# host: localhost
+# port: 3306
+# username: user
+# database: varo
+# password: sehrsicher
+# useSSL: false
+
time:
timezone: Europe/Berlin
startTime: '11:00'
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 8b3b392..216f298 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,4 +1,8 @@
name: Varo
version: @version@
main: org.wlosp.varo.VaroPlugin
-api-version: '1.16'
\ No newline at end of file
+api-version: '1.16'
+
+commands:
+ varo:
+ description: Generic varo command
\ No newline at end of file