diff --git a/.gitignore b/.gitignore
index 7c9cf9c..883f255 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/build/
/.gradle/
+/.idea/
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 7c82d64..3d6a125 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -31,5 +31,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index e8ff698..213d30d 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -14,6 +14,9 @@
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 97049d4..48365dc 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,8 +1,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
- kotlin("jvm") version "1.5.31"
+ kotlin("jvm") version "1.6.10"
id("com.github.johnrengelman.shadow") version "7.1.0"
+ kotlin("plugin.serialization") version "1.6.10"
}
group = "nl.kallestruik"
@@ -11,6 +12,7 @@ version = "2.0"
repositories {
mavenCentral()
mavenLocal()
+ jcenter()
maven("https://papermc.io/repo/repository/maven-public/")
maven("https://repo.aikar.co/content/groups/aikar/")
@@ -19,7 +21,9 @@ repositories {
dependencies {
implementation("co.aikar:acf-paper:0.5.0-SNAPSHOT")
compileOnly("com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT")
+ compileOnly("nl.kallestruik:DLib:1.4.1")
compileOnly(kotlin("stdlib-jdk8"))
+ compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
}
tasks.compileJava {
diff --git a/gradlew.bat b/gradlew.bat
index 107acd3..ac1b06f 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,89 +1,89 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/kotlin/nl/kallestruik/darena/DArena.kt b/src/main/kotlin/nl/kallestruik/darena/DArena.kt
index 2112a1e..8ccc134 100644
--- a/src/main/kotlin/nl/kallestruik/darena/DArena.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/DArena.kt
@@ -1,25 +1,174 @@
package nl.kallestruik.darena
-import nl.kallestruik.darena.managers.ConfigManager
-import nl.kallestruik.darena.managers.PointsManager
-import nl.kallestruik.darena.managers.TeamManager
+import co.aikar.commands.BukkitCommandExecutionContext
+import co.aikar.commands.InvalidCommandArgument
+import co.aikar.commands.MessageType
+import co.aikar.commands.PaperCommandManager
+import nl.kallestruik.darena.arenas.Arena
+import nl.kallestruik.darena.commands.CommandDArena
+import nl.kallestruik.darena.exceptions.TeamNotFoundException
+import nl.kallestruik.darena.listeners.*
+import nl.kallestruik.darena.managers.*
+import nl.kallestruik.darena.types.Reloadable
+import nl.kallestruik.darena.types.Team
+import org.bukkit.ChatColor
import org.bukkit.plugin.java.JavaPlugin
import java.io.File
-class DArena : JavaPlugin() {
+//TODO: Add documentation on a wiki page or something for this whole thing.
+
+class DArena: JavaPlugin() {
+ private lateinit var commandManager: PaperCommandManager
private lateinit var configManager: ConfigManager
private lateinit var pointsManager: PointsManager
private lateinit var teamManager: TeamManager
+ private lateinit var arenaManager: ArenaManager
+ private lateinit var editManager: EditManager
+ private lateinit var gameManager: GameManager
+
+ private lateinit var reloadable: Map
override fun onEnable() {
+ commandManager = PaperCommandManager(this)
+ commandManager.enableUnstableAPI("help")
+ commandManager.enableUnstableAPI("brigadier")
+
+
+
configManager = ConfigManager(File(dataFolder, "config.yml"))
configManager.load()
teamManager = TeamManager(File(dataFolder, "teams.yml"))
teamManager.load()
+
pointsManager = PointsManager(teamManager, File(dataFolder, "points.yml"))
+ pointsManager.load()
+
+ arenaManager = ArenaManager(teamManager, pointsManager, configManager, this, File(dataFolder, "arenas"), File(dataFolder, "arena.schema.json"))
+ arenaManager.loadArenas()
+
+ editManager = EditManager(arenaManager, this)
+ arenaManager.editManager = editManager
+
+ gameManager = GameManager(arenaManager, File(dataFolder, "games.yml"))
+ gameManager.load()
+
+ reloadable = mapOf(
+ Pair("arenas", arenaManager),
+ Pair("config", configManager),
+ Pair("games", gameManager),
+ )
+
+ setupCommandContexts()
+ setupCommandCompletions()
+ setupACFFormatting()
+
+ commandManager.registerCommand(CommandDArena(arenaManager, editManager, pointsManager, teamManager, gameManager))
+
+ server.pluginManager.registerEvents(CheckpointListener(arenaManager), this)
+ server.pluginManager.registerEvents(TeamListener(teamManager), this)
+ server.pluginManager.registerEvents(KillListener(arenaManager), this)
+ server.pluginManager.registerEvents(FireballListener(arenaManager, configManager), this)
+ server.pluginManager.registerEvents(FallListener(arenaManager), this)
+ server.pluginManager.registerEvents(BorderListener(arenaManager), this)
+ server.pluginManager.registerEvents(BuildRestrictionListener(arenaManager), this)
+ server.pluginManager.registerEvents(SectionListener(arenaManager), this)
+ server.pluginManager.registerEvents(CheckpointSoundListener(arenaManager), this)
+ server.pluginManager.registerEvents(CheckpointMessageListener(arenaManager), this)
+ server.pluginManager.registerEvents(GrapplingHookListener(arenaManager), this)
+ server.pluginManager.registerEvents(EditListener(editManager, this), this)
}
override fun onDisable() {
+ teamManager.save()
+ pointsManager.save()
+
+ }
+
+ //TODO: Move this to a more suitable location
+ private fun setupACFFormatting() {
+ commandManager.setFormat(
+ MessageType.INFO,
+ ChatColor.GRAY,
+ ChatColor.YELLOW,
+ ChatColor.RED
+ )
+ commandManager.setFormat(
+ MessageType.SYNTAX,
+ ChatColor.YELLOW,
+ ChatColor.GREEN,
+ ChatColor.WHITE
+ )
+ commandManager.setFormat(
+ MessageType.ERROR,
+ ChatColor.RED,
+ ChatColor.DARK_GREEN,
+ ChatColor.GREEN
+ )
+ commandManager.setFormat(
+ MessageType.HELP,
+ ChatColor.YELLOW,
+ ChatColor.AQUA,
+ ChatColor.GRAY
+ )
+ }
+
+ //TODO: Move this to a more suitable location
+ private fun setupCommandContexts() {
+ commandManager.commandContexts.registerContext(
+ Arena::class.java
+ ) { context: BukkitCommandExecutionContext ->
+ return@registerContext arenaManager.getArena(context.popFirstArg())
+ ?: throw InvalidCommandArgument("Arena does not exist!")
+ }
+
+ commandManager.commandContexts.registerContext(
+ Team::class.java
+ ) { context: BukkitCommandExecutionContext ->
+ try {
+ return@registerContext teamManager.getTeamByName(context.popFirstArg())
+ } catch (e: TeamNotFoundException) {
+ throw InvalidCommandArgument("Arena does not exist!")
+ }
+ }
+
+ commandManager.commandContexts.registerContext(
+ Reloadable::class.java
+ ) { context: BukkitCommandExecutionContext ->
+ return@registerContext reloadable[context.popFirstArg()]
+ ?: throw InvalidCommandArgument("That is not a reloadable component!")
+ }
+ }
+
+ //TODO: Move this to a more suitable location
+ private fun setupCommandCompletions() {
+ // Arena
+ commandManager.commandCompletions.registerAsyncCompletion("arena") {
+ arenaManager.getAllArenaNames()
+ }
+
+ commandManager.commandCompletions.setDefaultCompletion("arena", Arena::class.java)
+
+ // Team
+ commandManager.commandCompletions.registerAsyncCompletion("team") {
+ teamManager.getAllTeamNames()
+ }
+
+ commandManager.commandCompletions.setDefaultCompletion("team", Team::class.java)
+
+ // teamOptions
+ commandManager.commandCompletions.registerCompletion("teamOptions") {
+ listOf(
+ "color",
+ "prefix"
+ )
+ }
+
+ // Reloadable
+ commandManager.commandCompletions.registerAsyncCompletion("reloadable") {
+ reloadable.keys
+ }
+
+ commandManager.commandCompletions.setDefaultCompletion("reloadable", Reloadable::class.java)
}
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/arenas/Arena.kt b/src/main/kotlin/nl/kallestruik/darena/arenas/Arena.kt
index eebff5e..679c95d 100644
--- a/src/main/kotlin/nl/kallestruik/darena/arenas/Arena.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/arenas/Arena.kt
@@ -1,62 +1,192 @@
package nl.kallestruik.darena.arenas
+import net.kyori.adventure.text.Component
import nl.kallestruik.darena.arenas.world.ArenaWorld
+import nl.kallestruik.darena.exceptions.ArenaStartAbortedException
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.managers.ConfigManager
+import nl.kallestruik.darena.managers.PointsManager
+import nl.kallestruik.darena.managers.TeamManager
+import nl.kallestruik.darena.types.ArenaFeature
+import nl.kallestruik.darena.types.arena.ProcessedArenaCheckpoint
+import nl.kallestruik.darena.types.border.MCBorder
+import nl.kallestruik.darena.types.countdown.AsyncCountdown
+import nl.kallestruik.darena.types.countdown.AsyncCountdownWithSyncTask
import nl.kallestruik.darena.util.ArenaUtil
+import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.darena.util.RenderUtil
+import org.bukkit.Color
+import org.bukkit.GameMode
+import org.bukkit.World
+import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
-import org.bukkit.potion.PotionEffect
-import org.bukkit.potion.PotionEffectType
+import org.bukkit.scheduler.BukkitRunnable
class Arena(
- private val config: ArenaConfig,
- private val arenaUtil: ArenaUtil,
+ val config: ArenaConfig,
private val plugin: JavaPlugin,
- private val world: ArenaWorld = ArenaWorld(config.name, plugin),
+ private val configManager: ConfigManager,
+ private val arenaManager: ArenaManager,
+ private val teamManager: TeamManager,
+ private val pointsManager: PointsManager,
+ val world: ArenaWorld = ArenaWorld(config.name, configManager, plugin),
) {
- private lateinit var session: ArenaSession
- // Simple stuff done: 0.001474s
- // createArena start: 0.0001026s
- // Loaded schematic: 0.000162s
- // Created new world: 3.122034s
- // Loaded world: 0.0075989s
- // Placed arena: 2.3205267s
- // Fixed light: 4.2093585s
- // createArena done: 0.0002953
- // Countdown done: 5.0126158
- // Fully done: 0.0982557
+ var session: ArenaSession? = null
+ var pointsSession: PointsSession? = null
+
+ fun start(loadingTimeOverride: Int, spectatorTimeOverride: Int, beforeStartTimeOverride: Int) {
+ val loadingTime = if (loadingTimeOverride != -1) loadingTimeOverride else config.countdown.loadingTime
+ val spectatorTime = if (spectatorTimeOverride != -1) spectatorTimeOverride else config.countdown.spectatorTime
+ val beforeStartTime = if (beforeStartTimeOverride != -1) beforeStartTimeOverride else config.countdown.beforeStartTime
- fun start() {
- //TODO: Redo everything in here.
// Create a new session
session = ArenaSession()
+ pointsSession = PointsSession(teamManager, pointsManager, this)
// Add all participants and spectators
- session.participants.addAll(arenaUtil.getPossibleParticipants());
- session.spectators.addAll(arenaUtil.getPossibleSpectators());
- // Reset the world
- world.reset()
+ session!!.originalParticipants.addAll(ArenaUtil.getPossibleParticipants())
+ session!!.participants.addAll(session!!.originalParticipants)
+ session!!.spectators.addAll(ArenaUtil.getPossibleSpectators())
+ session!!.allPlayers.addAll(session!!.originalParticipants.union(session!!.spectators))
+ session!!.currentCountdown = AsyncCountdownWithSyncTask(plugin, loadingTime, "Teleporting in", { //TODO: Make the subtitle configurable
+ // Reset the world
+ world.reset()
+ }, {
+ session!!.processSpawnRules(config.spawns, config.spawnRules, config.loadouts)
+ session!!.processSpawnPools(config.spawnPools)
+ session!!.processCheckpoints(config.checkpoints)
+ startBorders()
- // Place all spectators in the arena
- session.spectators.forEach {
-// config.spectatorSpawn.spawn(world, it)
- }
+ // Spawn everyone as a spectator so they can explore the map.
+ for (player in session!!.allPlayers) {
+ player.gameMode = GameMode.SPECTATOR
+ }
- // Randomize spawns
- session.initialSpawns.addAll(config.spawns.filter {
- //TODO: make this filter only spawns that meet the intitial spawn condidtions. Or just replace this whole method.
- return@filter true
- }.keys)
- session.initialSpawns.shuffle()
+ val spectatorSpawnRule = session!!.spawnRules["spectator"] ?: throw ArenaStartAbortedException("Missing spawn rule \"spectator\". This rule is required.")
- // Spawn all the players
- session.participants.forEachIndexed { index, player ->
- // TODO: Freeze players in place (for in arena countdown) (if countdown is 0 dont freeze them)
- config.spawns[session.initialSpawns[index % session.initialSpawns.size]]!!.spawn(world, player)
- }
- // TODO:
+ spectatorSpawnRule.spawnPlayers(session!!.allPlayers, world)
+
+ session!!.currentCountdown = AsyncCountdown(plugin, spectatorTime, "Starting in", alwaysAsActionBar = true) { //TODO: Make the subtitle configurable
+ for (participant in session!!.participants) {
+ if (config.features.contains(ArenaFeature.GAMEMODE_ADVENTURE))
+ participant.gameMode = GameMode.ADVENTURE
+ else
+ participant.gameMode = GameMode.SURVIVAL
+ }
+
+ val initialSpawnRule = session!!.spawnRules["initial"] ?: throw ArenaStartAbortedException("Missing spawn rule \"initial\". This rule is required.")
+ initialSpawnRule.spawnPlayers(session!!.participants, world)
+
+ // Mark the game as in progress. This makes the event listeners all trigger from this point onwards.
+ session!!.isInProgress = true
+ session!!.currentCountdown = AsyncCountdown(plugin, beforeStartTime, "Beginning in", alwaysAsTitle = true) { //TODO: Make the subtitle configurable
+
+ config.blockRemoveZone?.removeBlocks(world)
+
+ startTimers()
+ }.start(session!!.allPlayers)
+ }.start(session!!.allPlayers)
+ }).start(session!!.allPlayers)
}
- fun reset() {
- world.reset()
+ fun startTimers() {
+ session!!.startTime = System.currentTimeMillis()
+ if (config.endConditions.time != -1) {
+ session!!.timeLeft = config.endConditions.time
+ session!!.endTimeTask = object : BukkitRunnable() {
+ override fun run() {
+ arenaManager.end()
+ }
+ }.runTaskLater(plugin, config.endConditions.time * 20L)
+ }
+
+ session!!.timerTask = object : BukkitRunnable() {
+ override fun run() {
+ session!!.timeLeft--
+ updateActionbar()
+ }
+ }.runTaskTimer(plugin, 20, 20)
+ }
+
+ fun startBorders() {
+ config.borders.vertical.getOrNull(0)?.execute(MCBorder(world), plugin)
+ config.borders.top.getOrNull(0)?.execute(session!!.borderTop, plugin)
+ config.borders.bottom.getOrNull(0)?.execute(session!!.borderBottom, plugin)
+
+ if (hasFeature(ArenaFeature.HORIZONTAL_BORDERS)) {
+ session!!.borderTask = BorderRunnable(world.world, session!!).runTaskTimer(plugin, 0, 10)
+ }
+ }
+
+ fun updateActionbar() {
+ var actionBar = Component.empty()
+ if (hasFeature(ArenaFeature.SHOW_TIMER))
+ actionBar = actionBar.append(Component
+ .text("Time left: ${ArenaUtil.formatTime(session!!.timeLeft)}"))
+ val allPlayers = session!!.originalParticipants.union(session!!.spectators)
+ for (player in allPlayers) {
+ player.sendActionBar(actionBar)
+ }
+ }
+
+ fun end() {
+ for (player in session!!.originalParticipants) {
+ ArenaUtil.clearPlayer(player)
+ player.gameMode = GameMode.SURVIVAL
+ session!!.spectators.remove(player)
+ }
+
+ world.empty()
+ if (pointsSession != null)
+ pointsSession!!.end()
+
+ session?.timerTask?.cancel()
+ session?.endTimeTask?.cancel()
+ session?.currentCountdown?.cancel()
+ session?.borderTask?.cancel()
+ }
+
+ fun hasFeature(feature: ArenaFeature) = config.features.contains(feature)
+
+ fun sendMessage(component: Component) {
+ for (player in session!!.allPlayers) {
+ player.sendMessage(component)
+ }
+ }
+
+ fun makeSpectator(player: Player) {
+ Logger.trace(Arena::class, "makeSpectator(player: ${player.name})")
+ player.gameMode = GameMode.SPECTATOR
+ session!!.participants.remove(player)
+ session!!.spectators.add(player)
+ }
+
+ fun respawn(player: Player) {
+ val lastCheckpoint: ProcessedArenaCheckpoint? = session!!.lastCheckpoint[player.uniqueId]
+ val spawnRule = if (lastCheckpoint != null)
+ lastCheckpoint.spawnRule!!
+ else
+ session!!.spawnRules["initial"]!!
+
+ spawnRule.spawnPlayer(player, world)
+ }
+
+ private class BorderRunnable(
+ private val world: World,
+ private val session: ArenaSession
+ ): BukkitRunnable() {
+
+ override fun run() {
+ RenderUtil.drawXZPlane(world,
+ -100, -100,
+ 100, 100,
+ session.borderTop.height, Color.RED)
+
+ RenderUtil.drawXZPlane(world,
+ -100, -100,
+ 100, 100,
+ session.borderBottom.height, Color.RED)
+ }
}
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/arenas/ArenaConfig.kt b/src/main/kotlin/nl/kallestruik/darena/arenas/ArenaConfig.kt
index 1b52db9..77d532f 100644
--- a/src/main/kotlin/nl/kallestruik/darena/arenas/ArenaConfig.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/arenas/ArenaConfig.kt
@@ -1,74 +1,85 @@
package nl.kallestruik.darena.arenas
-import nl.kallestruik.darena.types.arena.ArenaLoadout
-import nl.kallestruik.darena.types.arena.ArenaCheckpoint
-import nl.kallestruik.darena.types.arena.ArenaPoints
-import nl.kallestruik.darena.types.arena.ArenaSpawn
-import nl.kallestruik.darena.types.arena.ArenaSpawnRule
-import nl.kallestruik.darena.util.ConfigHelper
+import kotlinx.serialization.*
+import kotlinx.serialization.json.Json
+import nl.kallestruik.darena.types.ArenaFeature
+import nl.kallestruik.darena.types.arena.*
+import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.dlib.config.annotations.Description
import java.io.File
-import org.bukkit.configuration.InvalidConfigurationException
-import org.bukkit.configuration.file.YamlConfiguration
-import kotlin.collections.mutableMapOf
+@Serializable
data class ArenaConfig(
+ @Description(["The name of the arena."])
var name: String,
- var file: File,
+ @Description(["The spawns that the arena will use. These alone are not enough to make a player spawn (See spawnRules)."])
var spawns: MutableMap = mutableMapOf(),
+ @Description(["The loadouts for the arena. These determine the items a player spawns with and what potion effects they get."])
var loadouts: MutableMap = mutableMapOf(),
- var checkpoints: MutableMap = mutableMapOf(),
+ @Description(["The checkpoints for the arena. Both normal checkpoints and finish points are defined here."])
+ var checkpoints: MutableList = mutableListOf(),
+ @Description([
+ "The points that can be obtained by doing certain actions.",
+ "These points are always awarded top down with the last repeating.",
+ "For example when you set kill to 10, 5, and 0, the first player to get a kill will receive 10 points, the second 5, and every player after that will receive 0."])
var points: ArenaPoints = ArenaPoints(),
- var spawnRules: MutableMap = mutableMapOf()
+ @Description([
+ "The spawn rules used by the arena. These rules are used by many different components to link multiple spawns together with a set of items.",
+ "There are two special spawn rules that are required for the operation of the plugin. These are:",
+ "initial - Used by the plugin to do the initial round of spawning.",
+ "spectator - Used by the plugin to spawn the spectators."])
+ var spawnRules: MutableMap = mutableMapOf(),
+ @Description(["The list of features that are enabled in the arena."])
+ var features: MutableSet = mutableSetOf(),
+ @Description(["" +
+ "The different borders used by the arena. These are defined using border script. Below a short explanation of the different keyword:",
+ "CENTER - Set the center of the border to x,z. (ONLY AVAILABLE FOR VERTICAL BORDERS)",
+ "SET - On vertical borders set the with to i. On horizontal borders set the height to i.",
+ "TRANSITION - Same as SET , but over t seconds.",
+ "WAIT - Wait for t seconds."])
+ var borders: ArenaBorders = ArenaBorders(),
+ @SerialName("end-conditions")
+ @Description(["The end conditions of the arena."])
+ var endConditions: ArenaEndConditions = ArenaEndConditions(),
+ @SerialName("spawn-pools")
+ @Description(["The spawn pools that can be used by the arena. These are used to define groups of spawn rules that can be chosen at random by checkpoints."])
+ var spawnPools: MutableMap = mutableMapOf(),
+ @SerialName("break-allowed")
+ @Description(["The blocks that players are allowed to break in the arena. Unless specified otherwise all blocks can be broken."])
+ var breakAllowed: ArenaAllowList = ArenaAllowList(),
+ @SerialName("place-allowed")
+ @Description(["The blocks that players are allowed to place in the arena. Unless specified otherwise all blocks can be broken."])
+ var placeAllowed: ArenaAllowList = ArenaAllowList(),
+ @Description(["The different countdowns used by the arena."])
+ var countdown: ArenaCountdown = ArenaCountdown(),
+ @SerialName("block-remove-zone")
+ @Description(["A area of blocks to remove at the start of the game."])
+ var blockRemoveZone: ArenaBlockRemoveZone? = null,
) {
- fun save(toString: Boolean = false): String? {
- val config = YamlConfiguration()
- config.set("name", name)
+ @Transient
+ lateinit var file: File
- ConfigHelper.saveMap(ConfigHelper.getOrCreateSection(config, "spawns"), spawns)
- ConfigHelper.saveMap(ConfigHelper.getOrCreateSection(config, "loadouts"), loadouts)
- ConfigHelper.saveMap(ConfigHelper.getOrCreateSection(config, "checkpoints"), checkpoints)
- points.save(ConfigHelper.getOrCreateSection(config, "points"))
- ConfigHelper.saveMap(ConfigHelper.getOrCreateSection(config, "spawnRules"), spawnRules)
+ fun save(toString: Boolean = false): String? {
+ val json = Json {
+ prettyPrint = true
+ }.encodeToString(this)
if (toString) {
- return config.saveToString()
+ return json
}
- config.save(file)
+ file.writeText(json, Charsets.UTF_8)
return null
}
companion object {
- @Throws(InvalidConfigurationException::class)
fun load(file: File): ArenaConfig {
- val config = ConfigHelper.getOrCreateConfig(file, "template/arena.yml")
- if (!config.contains("name")) {
- throw InvalidConfigurationException("The arena configuration file '${file.name}' does not contain the required attribute 'name'")
- }
+ Logger.trace(ArenaConfig::class, "load(file: ${file.path})")
- val arenaConfig = ArenaConfig(config.getString("name")!!, file)
+ val config = Json.decodeFromString(file.readText(Charsets.UTF_8))
+ config.file = file
- if (config.contains("spawns")) {
- arenaConfig.spawns = ConfigHelper.loadMap(config.getConfigurationSection("spawns")!!, ArenaSpawn)
- }
-
- if (config.contains("loadouts")) {
- arenaConfig.loadouts = ConfigHelper.loadMap(config.getConfigurationSection("loadouts")!!, ArenaLoadout)
- }
-
- if (config.contains("checkpoints")) {
- arenaConfig.checkpoints = ConfigHelper.loadMap(config.getConfigurationSection("checkpoints")!!, ArenaCheckpoint)
- }
-
- if (config.contains("points")) {
- arenaConfig.points = ArenaPoints.load(config.getConfigurationSection("points")!!)
- }
-
- if (config.contains("spawnRules")) {
- arenaConfig.spawnRules = ConfigHelper.loadMap(config.getConfigurationSection("spawnRules")!!, ArenaSpawnRule)
- }
-
- return arenaConfig
+ return config
}
}
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/arenas/ArenaSession.kt b/src/main/kotlin/nl/kallestruik/darena/arenas/ArenaSession.kt
index 63fe4f8..d548e1b 100644
--- a/src/main/kotlin/nl/kallestruik/darena/arenas/ArenaSession.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/arenas/ArenaSession.kt
@@ -1,13 +1,81 @@
package nl.kallestruik.darena.arenas
-import nl.kallestruik.darena.types.arena.ArenaSpawn
+import nl.kallestruik.darena.exceptions.ArenaStartAbortedException
+import nl.kallestruik.darena.types.arena.*
+import nl.kallestruik.darena.types.border.HorizontalBorder
+import nl.kallestruik.darena.types.countdown.Countdown
import org.bukkit.entity.Player
-import java.util.UUID
+import org.bukkit.scheduler.BukkitTask
+import java.util.*
data class ArenaSession(
- val participants: MutableList = ArrayList(),
- val spectators: MutableList = ArrayList(),
- val completedObjective: MutableList = ArrayList(),
- val deaths: MutableList = ArrayList(),
- val initialSpawns: MutableList = ArrayList(),
-)
+ val originalParticipants: MutableList = mutableListOf(),
+ val participants: MutableList = mutableListOf(),
+ val spectators: MutableList = mutableListOf(),
+ val allPlayers: MutableList = mutableListOf(),
+ val completedObjective: MutableList = mutableListOf(),
+ val deaths: MutableList = mutableListOf(),
+ val spawnRules: MutableMap = mutableMapOf(),
+ val spawnPools: MutableMap = mutableMapOf(),
+ val checkpoints: MutableList = mutableListOf(),
+ val lastCheckpoint: MutableMap = mutableMapOf(),
+ val reachedCheckpoints: MutableMap> = mutableMapOf(),
+ val lastDamage: MutableMap = mutableMapOf(),
+ val borderTop: HorizontalBorder = HorizontalBorder(),
+ val borderBottom: HorizontalBorder = HorizontalBorder(),
+ val finishedSectionsByPlayer: MutableMap = mutableMapOf(),
+ val finishedPerSection: MutableMap = mutableMapOf(),
+ var startTime: Long = 0,
+ var playersFinished: Int = 0,
+ var timeLeft: Int = -1,
+ var endTimeTask: BukkitTask? = null,
+ var timerTask: BukkitTask? = null,
+ var borderTask: BukkitTask? = null,
+ var isInProgress: Boolean = false,
+ var currentCountdown: Countdown? = null,
+) {
+
+ fun processSpawnRules(spawns: Map, rules: Map, loadouts: Map) {
+ for (rule in rules) {
+ val list = mutableListOf()
+ for (spawn in rule.value.spawns) {
+ if (spawn in spawns)
+ list.add(spawns[spawn]!!)
+ else
+ throw ArenaStartAbortedException("Spawn $spawn is used in spawn rule ${rule.key} but is not defined. Please resolve this issue before trying to start this arena.")
+ }
+
+ list.shuffle()
+
+ if (!loadouts.containsKey(rule.value.loadout) && rule.value.loadout != "none")
+ throw ArenaStartAbortedException("Loadout ${rule.value.loadout} is used in spawn rule ${rule.key} but is not defined. Please resolve this issue before trying to start this arena.")
+
+ spawnRules[rule.key] = ProcessedArenaSpawnRule(rule.value, list, loadouts[rule.value.loadout])
+ }
+ }
+
+ fun processSpawnPools(pools: Map) {
+ for (pool in pools) {
+ val list = mutableListOf()
+ for (rule in pool.value.rules) {
+ if (rule in spawnRules)
+ list.add(spawnRules[rule]!!)
+ else
+ throw ArenaStartAbortedException("Spawn rule $rule is used in spawn pool ${pool.key} but is not defined. Please resolve this issue before trying to start this arena.")
+ }
+
+ list.shuffle()
+
+ spawnPools[pool.key] = ProcessedArenaSpawnPool(pool.value.label, pool.value.times, list)
+ }
+ }
+
+ fun processCheckpoints(checkpoints: List) {
+ for (checkpoint in checkpoints) {
+ val spawnRule = spawnRules[checkpoint.spawnRule]
+ val spawnPool = spawnPools[checkpoint.spawnPool]
+
+ this.checkpoints.add(ProcessedArenaCheckpoint(checkpoint, spawnRule, spawnPool))
+ }
+ }
+}
diff --git a/src/main/kotlin/nl/kallestruik/darena/arenas/PointsSession.kt b/src/main/kotlin/nl/kallestruik/darena/arenas/PointsSession.kt
new file mode 100644
index 0000000..bc94171
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/arenas/PointsSession.kt
@@ -0,0 +1,122 @@
+package nl.kallestruik.darena.arenas
+
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.TextColor
+import nl.kallestruik.darena.exceptions.TeamNotFoundException
+import nl.kallestruik.darena.managers.PointsManager
+import nl.kallestruik.darena.managers.TeamManager
+import nl.kallestruik.darena.types.Team
+import nl.kallestruik.darena.types.arena.ProcessedArenaCheckpoint
+import nl.kallestruik.darena.types.arena.ProcessedArenaSpawnPool
+import nl.kallestruik.darena.util.Logger
+import org.bukkit.entity.Player
+import java.util.*
+import kotlin.math.min
+
+class PointsSession(
+ private val teamManager: TeamManager,
+ private val pointsManager: PointsManager,
+ private val arena: Arena,
+) {
+ private val checkpointCompletionCount: MutableMap = mutableMapOf()
+ private var kills: Int = 0
+ // Team -> {reason -> amount}
+ private val points: MutableMap> = HashMap()
+
+ @Throws(TeamNotFoundException::class)
+ fun givePointsToPlayer(uuid: UUID, amount: Int, reason: String) {
+ Logger.trace(PointsSession::class, "givePointsToPlayer(uuid: $uuid, amount: $amount, reason: $reason)")
+ Logger.debug(PointsSession::class, "Giving $amount points to player with uuid $uuid for reason $reason")
+ val team = teamManager.getTeamForPlayer(uuid) ?: return
+
+ givePointsToTeam(team, amount, reason)
+ }
+
+ fun givePointsToTeam(team: Team, amount: Int, reason: String) {
+ Logger.trace(PointsSession::class, "givePointsToTeam(team: $team, amount: $amount, reason: $reason)")
+ Logger.debug(PointsSession::class, "Giving $amount points to team ${team.name} for reason $reason")
+ val pointsMap = points.getOrDefault(team, mutableMapOf())
+ pointsMap[reason] = (pointsMap[reason] ?: 0) + amount
+ points[team] = pointsMap
+ }
+
+ fun giveCheckpointPoints(player: Player, checkpoint: ProcessedArenaCheckpoint) {
+ Logger.debug(PointsSession::class, "Giving checkpoint points to player ${player.name} for reaching checkpoint ${checkpoint.label}")
+ val checkpointPointList = arena.config.points.checkpoints[checkpoint.label] ?: return
+ val checkpointPoints = checkpointPointList[min(checkpointCompletionCount[checkpoint.label] ?: 0, checkpointPointList.size - 1)]
+ givePointsToPlayer(player.uniqueId, checkpointPoints, "Reaching checkpoint") //TODO: Make these messages configurable
+ checkpointCompletionCount[checkpoint.label] = (checkpointCompletionCount[checkpoint.label] ?: 0) + 1
+ }
+
+ fun giveKillPoints(killer: Player) {
+ val killPointList = arena.config.points.kill
+ val killPoints = killPointList[min(kills, killPointList.size -1 )]
+ givePointsToPlayer(killer.uniqueId, killPoints, "Kill")//TODO: Make these messages configurable
+ kills++
+ }
+
+ fun giveLMSPoints() {
+ var lmsCounter = 0
+ // First award points to the players still alive
+ val lmsPoints = arena.config.points.lastManStanding
+ for (player in arena.session!!.participants) {
+ givePointsToPlayer(player.uniqueId, lmsPoints[min(lmsCounter, lmsPoints.size - 1)], "Placement") //TODO: Make these messages configurable
+ lmsCounter++
+ }
+ for (uuid in arena.session!!.deaths.reversed()) {
+ givePointsToPlayer(uuid, lmsPoints[min(lmsCounter, lmsPoints.size - 1)], "Placement") //TODO: Make these messages configurable
+ lmsCounter++
+ }
+ }
+
+ fun end() {
+ giveLMSPoints()
+
+ val map = mutableMapOf>()
+
+ for (entry in points) {
+ val team = entry.key;
+ var detailComponent = Component.text("${team.name} - Points\n")
+ var totalPoints = 0
+ for (reason in entry.value) {
+ if (reason.value <= 0)
+ continue
+
+ totalPoints += reason.value
+ detailComponent = detailComponent.append(Component.text("\n[+${reason.value}] ${reason.key}"))
+ }
+ if (totalPoints > 0)
+ map[team] = Pair(totalPoints, detailComponent)
+ }
+
+ val sortedMap = map.toList().sortedByDescending { (_, v) -> v.first }.toMap()
+
+ //TODO: Make these message configurable
+ arena.sendMessage(Component.text("============[ Summary ]============"))
+ for (entry in sortedMap) {
+ // Give points to the team.
+ pointsManager.givePointsToTeam(entry.key, entry.value.first)
+ arena.sendMessage(Component.empty()
+ .append(teamManager.getTeamComponent(entry.key))
+ .append(Component
+ .text(": "))
+ .append(Component
+ .text(pointsManager.getPointsForTeam(entry.key)))
+ .append(Component
+ .text(" [+${entry.value.first}]")
+ .hoverEvent(entry.value.second.asHoverEvent())
+ .color(TextColor.color(0x55FFFF)))
+ )
+ }
+ }
+
+ fun givePoolPoints(player: Player, spawnPool: ProcessedArenaSpawnPool, checkpointNumber: Int) {
+ Logger.debug(PointsSession::class, "Giving pool points to player ${player.name} for reaching checkpoint $checkpointNumber in the pool ${spawnPool.label}")
+ val poolPointList = (arena.config.points.pools[spawnPool.label] ?: return)[checkpointNumber - 1]
+ val poolPoints = poolPointList[min(arena.session!!.finishedPerSection[checkpointNumber] ?: 0, poolPointList.size - 1)]
+ givePointsToPlayer(player.uniqueId, poolPoints, "Reaching checkpoint") //TODO: Make these messages configurable
+ arena.session!!.finishedPerSection[checkpointNumber] = (arena.session!!.finishedPerSection[checkpointNumber] ?: 0) + 1
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorld.kt b/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorld.kt
index e5a25a1..112d80c 100644
--- a/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorld.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorld.kt
@@ -3,46 +3,68 @@ package nl.kallestruik.darena.arenas.world
import nl.kallestruik.darena.exceptions.ArenaWorldCreationException
import nl.kallestruik.darena.exceptions.ArenaWorldLoadException
import nl.kallestruik.darena.exceptions.ArenaWorldSaveException
+import nl.kallestruik.darena.managers.ConfigManager
import org.bukkit.Bukkit
+import org.bukkit.Location
import org.bukkit.World
+import org.bukkit.WorldCreator
import org.bukkit.plugin.java.JavaPlugin
+import java.io.File
import java.nio.file.Files
import java.nio.file.Path
+import kotlin.io.path.exists
class ArenaWorld(
val name: String,
- val plugin: JavaPlugin
+ val configManager: ConfigManager,
+ val plugin: JavaPlugin,
) {
- var world: World
+ lateinit var world: World
+ val worldFolder = File(Bukkit.getServer().worldContainer, name)
init {
load()
- world = Bukkit.createWorld(ArenaWorldCreator(name))
+ createWorld()
+ world.isAutoSave = false
+ }
+
+ @Throws(ArenaWorldCreationException::class)
+ private fun createWorld() {
+ world = WorldCreator.name(name).generator(ArenaChunkGenerator()).createWorld()
?: throw ArenaWorldCreationException("Exception while creating bukkit world for arena \"$name\".")
}
+
@Throws(ArenaWorldLoadException::class)
fun reset() {
- //TODO: Teleport everyone out.
+ empty()
+ Bukkit.unloadWorld(world, false)
load()
+ createWorld()
}
@Throws(ArenaWorldSaveException::class)
fun save() {
+ world.save()
try {
val savePath = Path.of(plugin.dataFolder.path, "worlds", name)
- Files.walk(savePath).use { walk ->
- walk.sorted(Comparator.reverseOrder()).forEach { path ->
- Files.delete(path)
- }
- }
- Files.walk(world.worldFolder.toPath()).use { walk ->
+ if (savePath.exists())
+ Files.walk(savePath).use { walk ->
+ walk.sorted(Comparator.reverseOrder()).forEach { path ->
+ Files.delete(path)
+ }
+ }
+
+ Files.walk(worldFolder.toPath()).use { walk ->
walk.forEach { source ->
val destination = Path.of(
savePath.toString(), source.toString()
- .substring(world.worldFolder.path.length)
+ .substring(worldFolder.path.length)
)
+ // Don't copy uid.dat files because they stop bukkit from loading the world.
+ if (source.endsWith("uid.dat"))
+ return@forEach
Files.copy(source, destination)
}
@@ -56,7 +78,10 @@ class ArenaWorld(
private fun load() {
try {
val loadPath = Path.of(plugin.dataFolder.path, "worlds", name)
- Files.walk(world.worldFolder.toPath()).use { walk ->
+ if (!loadPath.exists())
+ return
+
+ Files.walk(worldFolder.toPath()).use { walk ->
walk.sorted(Comparator.reverseOrder()).forEach { path ->
Files.delete(path)
}
@@ -65,9 +90,12 @@ class ArenaWorld(
Files.walk(loadPath).use { walk ->
walk.forEach { source ->
val destination = Path.of(
- world.worldFolder.path, source.toString()
+ worldFolder.path, source.toString()
.substring(loadPath.toString().length)
)
+ // Don't copy uid.dat files because they stop bukkit from loading the world.
+ if (source.endsWith("uid.dat"))
+ return@forEach
Files.copy(source, destination)
}
@@ -76,4 +104,11 @@ class ArenaWorld(
throw ArenaWorldLoadException("There was an issue load the world for arena \"$name\"!", e)
}
}
+
+ fun empty() {
+ for (player in world.players) {
+ player.spigot().respawn()
+ player.teleport(Location(Bukkit.getWorld(configManager.spawnWorldName), configManager.spawnPos.x + 0.5, configManager.spawnPos.y + 0.5, configManager.spawnPos.z + 0.5))
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorldConfig.kt b/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorldConfig.kt
deleted file mode 100644
index 9ef3dcd..0000000
--- a/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorldConfig.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package nl.kallestruik.darena.arenas.world
-
-data class ArenaWorldConfig(
- val name: String,
-)
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorldCreator.kt b/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorldCreator.kt
deleted file mode 100644
index b0ef32d..0000000
--- a/src/main/kotlin/nl/kallestruik/darena/arenas/world/ArenaWorldCreator.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package nl.kallestruik.darena.arenas.world
-
-import org.bukkit.WorldCreator
-import org.bukkit.generator.ChunkGenerator
-
-class ArenaWorldCreator(
- name: String
-): WorldCreator(name) {
- private var generator: ChunkGenerator = ArenaChunkGenerator()
-
- override fun generator(): ChunkGenerator {
- return generator
- }
-
-
-}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/commands/CommandDArena.kt b/src/main/kotlin/nl/kallestruik/darena/commands/CommandDArena.kt
new file mode 100644
index 0000000..2476c35
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/commands/CommandDArena.kt
@@ -0,0 +1,354 @@
+package nl.kallestruik.darena.commands
+
+import co.aikar.commands.BaseCommand
+import co.aikar.commands.CommandHelp
+import co.aikar.commands.annotation.*
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.event.ClickEvent
+import net.kyori.adventure.text.format.NamedTextColor
+import net.kyori.adventure.text.format.TextColor
+import nl.kallestruik.darena.arenas.Arena
+import nl.kallestruik.darena.exceptions.ArenaCreationException
+import nl.kallestruik.darena.exceptions.ArenaEditException
+import nl.kallestruik.darena.exceptions.TeamExistsException
+import nl.kallestruik.darena.managers.*
+import nl.kallestruik.darena.types.EditSetting
+import nl.kallestruik.darena.types.Reloadable
+import nl.kallestruik.darena.types.Team
+import nl.kallestruik.darena.util.Logger
+import org.bukkit.ChatColor
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+@CommandAlias("darena|arena")
+class CommandDArena(
+ private val arenaManager: ArenaManager,
+ private val editManager: EditManager,
+ private val pointsManager: PointsManager,
+ private val teamManager: TeamManager,
+ private val gameManager: GameManager,
+): BaseCommand() {
+
+ @HelpCommand
+ fun doHelp(sender: CommandSender, help: CommandHelp) {
+ Logger.trace(CommandDArena::class, "doHelp(sender: $sender, help: $help)")
+ help.showHelp()
+ }
+
+ @Subcommand("start|s")
+ @Description("Start an arena")
+ @CommandPermission("darena.start")
+ fun onStart(sender: CommandSender, arena: Arena, @Default("-1") loadingTimeOverride: Int, @Default("-1") spectatorTimeOverride: Int, @Default("-1") beforeStartTimeOverride: Int) {
+ Logger.trace(CommandDArena::class, "onStart(sender: $sender)")
+ arenaManager.start(arena, loadingTimeOverride, spectatorTimeOverride, beforeStartTimeOverride)
+ }
+
+ @Subcommand("end")
+ @Description("End the current arena")
+ @CommandPermission("darena.end")
+ fun onEnd(sender: CommandSender) {
+ Logger.trace(CommandDArena::class, "onEnd(sender: $sender)")
+ arenaManager.end()
+ }
+
+ @Subcommand("list")
+ @Description("List all arenas")
+ @CommandPermission("darena.list")
+ fun onList(sender: CommandSender) {
+ Logger.trace(CommandDArena::class, "onList(sender: $sender)")
+ sender.sendMessage(Component.text("============[ Arenas ]============"))
+ for (gameEntry in gameManager.games) {
+ sender.sendMessage(Component
+ .text("${gameEntry.key}:")
+ .hoverEvent(Component.text(gameEntry.value.description))
+ )
+ for (arena in gameEntry.value.arenas) {
+ sender.sendMessage(Component.empty()
+ .append(Component
+ .text(" ${arena.config.name} "))
+ .append(Component
+ .text("[start]")
+ .clickEvent(ClickEvent.runCommand("/darena start ${arena.config.name}"))
+ .color(NamedTextColor.GREEN))
+ .append(Component
+ .text(" "))
+ .append(Component
+ .text("[edit]")
+ .clickEvent(ClickEvent.runCommand("/darena edit load ${arena.config.name}"))
+ .color(NamedTextColor.AQUA))
+ )
+ }
+ }
+ }
+
+ @Subcommand("leaderboard|top")
+ @CommandAlias("leaderboard")
+ @Description("Show the points leaderboard")
+ @CommandPermission("darena.leaderboard")
+ fun onLeaderboard(sender: CommandSender) {
+ Logger.trace(CommandDArena::class, "onLeaderboard(sender: $sender)")
+ pointsManager.sendLeaderboard(sender)
+ }
+
+ @Subcommand("reload")
+ @Description("Reload the config")
+ @CommandPermission("darena.reload")
+ fun onReload(sender: CommandSender, reloadable: Reloadable) {
+ Logger.trace(CommandDArena::class, "onReload(sender: $sender, reloadable: $reloadable)")
+ reloadable.reload()
+ sender.sendMessage("${ChatColor.YELLOW}[?] Successfully reloaded")
+ }
+
+ @Subcommand("new")
+ @Description("Create a new arena")
+ @CommandPermission("darena.new")
+ fun onNew(sender: CommandSender, name: String) {
+ Logger.trace(CommandDArena::class, "onNew(sender: $sender, name: $name)")
+ try {
+ arenaManager.createArena(name)
+ sender.sendMessage("${ChatColor.YELLOW}[?] Arena $name created successfully. You can edit it with /darena edit load $name")
+ } catch (e: ArenaCreationException) {
+ sender.sendMessage("${ChatColor.RED}[!] ${e.message}}")
+ }
+ }
+
+ @Subcommand("edit")
+ inner class EditCommand: BaseCommand() {
+
+ @Subcommand("load")
+ @Description("Load and teleport you to the world of an arena for editing.")
+ @CommandPermission("darena.edit.load")
+ fun onLoad(player: Player, arena: Arena) {
+ Logger.trace(EditCommand::class, "onLoad(player: $player, arena: ${arena.config.name})")
+ try {
+ editManager.load(arena)
+ editManager.tp(player)
+ } catch (e: ArenaEditException) {
+ player.sendMessage("${ChatColor.RED}[!] ${e.message}")
+ }
+ }
+
+ @Subcommand("save")
+ @Description("Save the world currently being edited.")
+ @CommandPermission("darena.edit.load")
+ fun onSave(sender: CommandSender) {
+ Logger.trace(EditCommand::class, "onSave(sender: $sender)")
+ try {
+ editManager.save()
+ } catch (e: ArenaEditException) {
+ sender.sendMessage("${ChatColor.RED}[!] ${e.message}")
+ }
+ }
+
+ @Subcommand("tp")
+ @Description("Teleport into the world currently being edited.")
+ @CommandPermission("darena.edit.tp")
+ fun onTp(player: Player) {
+ Logger.trace(EditCommand::class, "onTp(player: $player)")
+ try {
+ editManager.tp(player)
+ } catch (e: ArenaEditException) {
+ player.sendMessage("${ChatColor.RED}[!] ${e.message}")
+ }
+ }
+
+ @Subcommand("finish")
+ @Description("Save and stop editing the world currently being edited.")
+ @CommandPermission("darena.edit.finish")
+ fun onFinish(sender: CommandSender) {
+ Logger.trace(EditCommand::class, "onFinish(sender: $sender)")
+ try {
+ editManager.finish()
+ } catch (e: ArenaEditException) {
+ sender.sendMessage("${ChatColor.RED}[!] ${e.message}")
+ }
+ }
+
+ @Subcommand("config")
+ @Description("Load and teleport you to the world of an arena for editing.")
+ @CommandPermission("darena.edit.load")
+ fun onConfig(player: Player, path: String) {
+ Logger.trace(EditCommand::class, "onConfig(player: $player, path: $path)")
+ player.sendMessage("This feature is currently disabled.")
+// try {
+// val info = editManager.getConfigValue(path)
+// player.sendMessage("------------------")
+// player.sendMessage("Name: ${info.name}")
+// player.sendMessage("Value: ${info.value}")
+// player.sendMessage("Description: ${info.description}")
+// player.sendMessage("------------------")
+// } catch (e: ArenaEditException) {
+// player.sendMessage("${ChatColor.RED}[!] ${e.message}")
+// }
+ }
+
+ @Subcommand("visualize")
+ inner class VisualizeCommand: BaseCommand() {
+ @Subcommand("spawns")
+ @Description("Toggle the visibility of all spawns")
+ @CommandPermission("darena.edit.visualize.spawns")
+ fun onSpawns(player: Player) {
+ editManager.toggleSpawnVisibility()
+ }
+
+ @Subcommand("checkpoints")
+ @Description("Toggle the visibility of all checkpoints")
+ @CommandPermission("darena.edit.visualize.checkpoints")
+ fun onCheckpoints(player: Player) {
+ editManager.toggleCheckpointVisibility()
+ }
+ }
+
+ @Subcommand("toggle")
+ inner class ToggleCommand: BaseCommand() {
+ @Subcommand("spawnplacing")
+ @Description("Toogle placing spawns with armor stands")
+ @CommandPermission("darena.edit.toggle.spawnplacing")
+ fun onSpawnPlacing(player: Player) {
+ val newValue = editManager.toggleFeature(player, EditSetting.SPAWN_PLACING)
+ player.sendMessage("Spawn placing is now: $newValue")
+ }
+ }
+
+ }
+
+ @Subcommand("team")
+ inner class TeamCommand: BaseCommand() {
+
+ @Subcommand("create")
+ @Description("Create a team.")
+ @CommandPermission("darena.team.create")
+ fun onCreate(sender: CommandSender, teamName: String) {
+ Logger.trace(TeamCommand::class, "onCreate(sender: $sender, teamName: $teamName)")
+ try {
+ teamManager.createTeam(teamName)
+ sender.sendMessage("${ChatColor.YELLOW}[?] Team $teamName was successfully created.")
+ } catch (e: TeamExistsException) {
+ sender.sendMessage("${ChatColor.RED}[!] ${e.message}")
+ }
+ }
+
+ @Subcommand("join|add-player")
+ @Description("Add a player to a team.")
+ @CommandPermission("darena.team.join")
+ @CommandCompletion("@team @players")
+ fun onAddPlayer(sender: CommandSender, team: Team, player: Player) {
+ Logger.trace(TeamCommand::class, "onAddPlayer(sender: $sender, team: $team, player: $player)")
+ if (teamManager.getTeamForPlayer(player.uniqueId) != null) {
+ sender.sendMessage("${ChatColor.RED}[!] Player is already in a team!")
+ return
+ }
+
+ teamManager.addPlayerToTeam(team, player.uniqueId)
+ sender.sendMessage("${ChatColor.YELLOW}[?] Player was successfully added to team.")
+ }
+
+ @Subcommand("leave|remove-player")
+ @Description("Remove a player from a team.")
+ @CommandPermission("darena.team.leave")
+ @CommandCompletion("@players")
+ fun onRemovePlayer(sender: CommandSender, player: Player) {
+ Logger.trace(TeamCommand::class, "onRemovePlayer(sender: $sender, player: $player)")
+ if (teamManager.getTeamForPlayer(player.uniqueId) == null) {
+ sender.sendMessage("${ChatColor.RED}[!] Player is not in a team!")
+ return
+ }
+
+ teamManager.removePlayerFromTeam(player.uniqueId)
+ sender.sendMessage("${ChatColor.YELLOW}[?] Player was successfully removed from their team.")
+ }
+
+ @Subcommand("remove")
+ @Description("Remove a team.")
+ @CommandPermission("darena.team.remove")
+ fun onRemove(sender: CommandSender, team: Team) {
+ Logger.trace(TeamCommand::class, "onRemove(sender: $sender, team: $team)")
+ teamManager.removeTeam(team)
+ sender.sendMessage("${ChatColor.YELLOW}[?] Team ${team.name} was successfully removed.")
+ }
+
+ @Subcommand("remove-all")
+ @Description("Remove all teams.")
+ @CommandPermission("darena.team.remove-all")
+ fun onRemoveAll(sender: CommandSender) {
+ Logger.trace(TeamCommand::class, "onRemoveAll(sender: $sender)")
+ val count = teamManager.removeAllTeams()
+ pointsManager.clear()
+ sender.sendMessage("${ChatColor.YELLOW}[?] Removed $count teams.")
+ }
+
+ @Subcommand("auto")
+ @Description("Remove all current teams and enable/disable automatic team mode.")
+ @CommandPermission("darena.team.auto")
+ fun onAuto(sender: CommandSender, enable: Boolean) {
+ Logger.trace(TeamCommand::class, "onAuto(sender: $sender, enable: $enable)")
+ teamManager.autoTeams = enable
+ teamManager.removeAllTeams()
+ if (enable)
+ teamManager.autoTeam()
+
+ sender.sendMessage("${ChatColor.YELLOW}[?] Automatic team creation is now ${if (enable) ("${ChatColor.GREEN}enabled") else "${ChatColor.RED}disabled"}")
+ }
+
+ @Subcommand("option")
+ @Description("Change team options.")
+ @CommandPermission("darena.team.option")
+ fun onOption(sender: CommandSender, team: Team, @Values("@teamOptions") option: String, value: String) {
+ Logger.trace(TeamCommand::class, "onOption(sender: $sender, option: $option, value: $value)")
+ try {
+ when (option.lowercase()) {
+ "color" -> team.color = TextColor.fromCSSHexString(value)!!
+ "prefix" -> team.prefix = value.replace("\"", "")
+ else -> sender.sendMessage("${ChatColor.RED}[!] Option $option does not exist!")
+ }
+ } catch (e: Exception) {
+ sender.sendMessage("${ChatColor.RED}[!] Something went wrong while trying to update the value of $option!")
+ }
+ }
+
+ @Subcommand("list")
+ @Description("Get a list of all teams and their members.")
+ @CommandPermission("darena.team.list")
+ fun onList(sender: CommandSender) {
+ Logger.trace(TeamCommand::class, "onList(sender: $sender)")
+ //TODO: Make these configurable
+ sender.sendMessage("============[ Teams ]============")
+ for (teamName in teamManager.getAllTeamNames()) {
+ val team = teamManager.getTeamByName(teamName)
+
+ sender.sendMessage(teamManager.getTeamComponent(team))
+ }
+ }
+ }
+
+ @Subcommand("points")
+ inner class PointsCommand: BaseCommand() {
+
+ @Subcommand("add")
+ @Description("Add points to a team.")
+ @CommandPermission("darena.points.add")
+ fun onAdd(sender: CommandSender, team: Team, amount: Int) {
+ Logger.trace(PointsCommand::class, "onAdd(sender: $sender, team: ${team.name}, amount: $amount)")
+ pointsManager.givePointsToTeam(team, amount)
+ sender.sendMessage("${ChatColor.YELLOW}[?] Added $amount points to the score of ${team.name}")
+ }
+
+ @Subcommand("remove")
+ @Description("Remove points from a team.")
+ @CommandPermission("darena.points.remove")
+ fun onRemove(sender: CommandSender, team: Team, amount: Int) {
+ Logger.trace(PointsCommand::class, "onRemove(sender: $sender, team: ${team.name}, amount: $amount)")
+ pointsManager.givePointsToTeam(team, -amount)
+ sender.sendMessage("${ChatColor.YELLOW}[?] Removed $amount points from the score of ${team.name}")
+ }
+
+ @Subcommand("reset")
+ @Description("Reset everyones score.")
+ @CommandPermission("darena.points.reset")
+ fun onReset(sender: CommandSender) {
+ Logger.trace(PointsCommand::class, "onReset(sender: $sender)")
+ pointsManager.clear()
+ sender.sendMessage("${ChatColor.YELLOW}[?] Reset everyone's score")
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/events/CheckpointReachedEvent.kt b/src/main/kotlin/nl/kallestruik/darena/events/CheckpointReachedEvent.kt
new file mode 100644
index 0000000..ba1ee86
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/events/CheckpointReachedEvent.kt
@@ -0,0 +1,26 @@
+package nl.kallestruik.darena.events
+
+import nl.kallestruik.darena.arenas.Arena
+import nl.kallestruik.darena.types.arena.ProcessedArenaCheckpoint
+import org.bukkit.entity.Player
+import org.bukkit.event.HandlerList
+import org.bukkit.event.player.PlayerEvent
+
+class CheckpointReachedEvent(
+ player: Player,
+ val arena: Arena,
+ val checkpoint: ProcessedArenaCheckpoint
+): PlayerEvent(player) {
+ override fun getHandlers(): HandlerList {
+ return HANDLERS_LIST;
+ }
+
+ companion object {
+ private val HANDLERS_LIST = HandlerList()
+
+ @JvmStatic
+ fun getHandlerList(): HandlerList {
+ return HANDLERS_LIST
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaCreationException.kt b/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaCreationException.kt
new file mode 100644
index 0000000..740ba16
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaCreationException.kt
@@ -0,0 +1,6 @@
+package nl.kallestruik.darena.exceptions
+
+class ArenaCreationException(
+ message: String? = null,
+ cause: Throwable? = null
+): Exception(message, cause)
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaEditException.kt b/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaEditException.kt
new file mode 100644
index 0000000..638ae37
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaEditException.kt
@@ -0,0 +1,6 @@
+package nl.kallestruik.darena.exceptions
+
+class ArenaEditException(
+ message: String? = null,
+ cause: Throwable? = null
+): Exception(message, cause)
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaEndAbortedException.kt b/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaEndAbortedException.kt
new file mode 100644
index 0000000..c3732eb
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaEndAbortedException.kt
@@ -0,0 +1,6 @@
+package nl.kallestruik.darena.exceptions
+
+class ArenaEndAbortedException(
+ message: String? = null,
+ cause: Throwable? = null
+): Exception(message, cause)
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaStartAbortedException.kt b/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaStartAbortedException.kt
new file mode 100644
index 0000000..a5d44bf
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/exceptions/ArenaStartAbortedException.kt
@@ -0,0 +1,6 @@
+package nl.kallestruik.darena.exceptions
+
+class ArenaStartAbortedException(
+ message: String? = null,
+ cause: Throwable? = null
+): Exception(message, cause)
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/exceptions/PotionEffectNotFoundException.kt b/src/main/kotlin/nl/kallestruik/darena/exceptions/PotionEffectNotFoundException.kt
new file mode 100644
index 0000000..a5d3757
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/exceptions/PotionEffectNotFoundException.kt
@@ -0,0 +1,6 @@
+package nl.kallestruik.darena.exceptions
+
+class PotionEffectNotFoundException(
+ message: String? = null,
+ cause: Throwable? = null
+): Exception(message, cause)
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/BlockBreakListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/BlockBreakListener.kt
new file mode 100644
index 0000000..15721b7
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/BlockBreakListener.kt
@@ -0,0 +1,21 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.block.BlockBreakEvent
+
+class BlockBreakListener(
+ val arenaManager: ArenaManager
+): Listener {
+
+ @EventHandler
+ fun onBlockBreak(event: BlockBreakEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.NO_TILE_DROPS, event.player)) {
+ return
+ }
+
+ event.isCancelled = true
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/BorderListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/BorderListener.kt
new file mode 100644
index 0000000..48b8c07
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/BorderListener.kt
@@ -0,0 +1,24 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.player.PlayerMoveEvent
+
+class BorderListener(
+ private val arenaManager: ArenaManager
+): Listener {
+
+ @EventHandler
+ fun onMove(event: PlayerMoveEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.HORIZONTAL_BORDERS, event.player))
+ return
+
+ if (event.to.y < arenaManager.currentArena!!.session!!.borderBottom.height)
+ event.player.health = 0.0
+
+ if (event.to.y > arenaManager.currentArena!!.session!!.borderTop.height)
+ event.player.health = 0.0
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/BuildRestrictionListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/BuildRestrictionListener.kt
new file mode 100644
index 0000000..f4fe741
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/BuildRestrictionListener.kt
@@ -0,0 +1,33 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.block.BlockBreakEvent
+import org.bukkit.event.block.BlockPlaceEvent
+
+class BuildRestrictionListener(
+ private val arenaManager: ArenaManager,
+): Listener {
+
+ @EventHandler
+ fun onBlockPlace(event: BlockPlaceEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.RESTRICT_BUILDING, event.player))
+ return
+
+ if (!arenaManager.currentArena!!.config.placeAllowed.isAllowed(event.blockPlaced.type)) {
+ event.isCancelled = true;
+ }
+ }
+
+ @EventHandler
+ fun onBlockBreak(event: BlockBreakEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.RESTRICT_BUILDING, event.player))
+ return
+
+ if (!arenaManager.currentArena!!.config.breakAllowed.isAllowed(event.block.type)) {
+ event.isCancelled = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointListener.kt
new file mode 100644
index 0000000..1dbe3e8
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointListener.kt
@@ -0,0 +1,52 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.events.CheckpointReachedEvent
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import nl.kallestruik.darena.util.ArenaUtil
+import org.bukkit.Bukkit
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.player.PlayerMoveEvent
+
+class CheckpointListener(
+ private val arenaManager: ArenaManager,
+): Listener {
+ @EventHandler
+ fun onMove(event: PlayerMoveEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.CHECKPOINTS, event.player))
+ return
+
+ val arena = arenaManager.currentArena!!
+ val uuid = event.player.uniqueId
+ val reachedCheckpoints = arena.session!!.reachedCheckpoints[uuid] ?: mutableSetOf()
+
+ for (checkpoint in arena.session!!.checkpoints) {
+ if (reachedCheckpoints.contains(checkpoint.label))
+ continue
+
+ if (!checkpoint.isInside(event.to))
+ continue
+
+ reachedCheckpoints.add(checkpoint.label)
+ arena.session!!.lastCheckpoint[uuid] = checkpoint
+ arena.session!!.reachedCheckpoints[uuid] = reachedCheckpoints
+
+ arena.pointsSession!!.giveCheckpointPoints(event.player, checkpoint)
+
+ // Call event to let other handlers do further actions with it.
+ Bukkit.getPluginManager().callEvent(CheckpointReachedEvent(event.player, arena, checkpoint))
+
+ if (checkpoint.movePlayer) {
+ checkpoint.resolveSpawnRule()
+ if (checkpoint.spawnRule != null)
+ checkpoint.spawnRule!!.spawnPlayer(event.player, arena.world)
+ }
+
+ if (checkpoint.isFinish)
+ ArenaUtil.finishPlayer(event.player, arena, arenaManager)
+
+ break
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointMessageListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointMessageListener.kt
new file mode 100644
index 0000000..dd62d25
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointMessageListener.kt
@@ -0,0 +1,26 @@
+package nl.kallestruik.darena.listeners
+
+import net.kyori.adventure.text.Component
+import nl.kallestruik.darena.events.CheckpointReachedEvent
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import nl.kallestruik.darena.util.ArenaUtil
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+
+class CheckpointMessageListener(
+ val arenaManager: ArenaManager
+): Listener {
+
+ @EventHandler
+ fun onCheckpointReached(event: CheckpointReachedEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.CHECKPOINT_MESSAGE, event.player)) {
+ return
+ }
+
+ val timeSinceStart = System.currentTimeMillis() - event.arena.session!!.startTime
+
+ //TODO: Make this message configurable.
+ event.arena.sendMessage(Component.text("${event.player.name} has reached a checkpoint! [${ArenaUtil.formatTimeMillis(timeSinceStart)}]"))
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointSoundListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointSoundListener.kt
new file mode 100644
index 0000000..cdc49d4
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/CheckpointSoundListener.kt
@@ -0,0 +1,24 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.events.CheckpointReachedEvent
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import org.bukkit.Sound
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+
+class CheckpointSoundListener(
+ val arenaManager: ArenaManager
+): Listener {
+
+ @EventHandler
+ fun onCheckpointReached(event: CheckpointReachedEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.CHECKPOINT_SOUND, event.player)) {
+ return
+ }
+
+ event.player.let {
+ it.playSound(it.location, Sound.BLOCK_NOTE_BLOCK_BELL, 1.0F, 1.0F)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/EditListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/EditListener.kt
new file mode 100644
index 0000000..74c7339
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/EditListener.kt
@@ -0,0 +1,168 @@
+package nl.kallestruik.darena.listeners
+
+import net.kyori.adventure.text.Component
+import nl.kallestruik.darena.managers.EditManager
+import nl.kallestruik.darena.types.EditSetting
+import nl.kallestruik.darena.types.arena.ArenaSpawn
+import nl.kallestruik.dlib.gui.chestGUI
+import nl.kallestruik.dlib.gui.textInputDialog
+import org.bukkit.Material
+import org.bukkit.entity.ArmorStand
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.entity.EntityDamageByEntityEvent
+import org.bukkit.event.entity.EntityPlaceEvent
+import org.bukkit.event.player.PlayerInteractAtEntityEvent
+import org.bukkit.plugin.Plugin
+
+class EditListener(
+ val editManager: EditManager,
+ val plugin: Plugin
+): Listener {
+
+ @EventHandler
+ fun onEntityPlace(event: EntityPlaceEvent) {
+ if (!editManager.shouldProcessEvent(event.player, EditSetting.SPAWN_PLACING))
+ return
+
+ val entity = event.entity
+
+ if (entity !is ArmorStand)
+ return
+
+ val loc = entity.location
+
+ val spawn = ArenaSpawn(loc.x, loc.y, loc.z, loc.yaw, loc.pitch)
+
+ textInputDialog(event.player!!, plugin) {
+ prompt = Component.text("Spawn name?")
+
+ item {
+ material = Material.PAPER
+ amount = 1
+ }
+
+ onFinish { name ->
+ if (editManager.addSpawn(name, spawn)) {
+ event.player?.sendMessage("Added spawn $name")
+ } else {
+ event.player?.sendMessage("Adding spawn failed. Is the name already in use?")
+ }
+ event.entity.remove()
+ }
+
+ onCancel {
+ event.player?.sendMessage("Canceled adding spawn")
+ event.entity.remove()
+ }
+ }
+ }
+
+ @EventHandler
+ fun onEntityDamage(event: EntityDamageByEntityEvent) {
+ if (event.damager !is Player)
+ return
+
+ val player = event.damager as Player
+
+ if (!editManager.shouldProcessEvent(player, EditSetting.ALWAYS)) {
+ return
+ }
+
+ if (event.entity is ArmorStand) {
+ entityDamageArmorStand(player, event)
+ }
+ }
+
+ private fun entityDamageArmorStand(player: Player, event: EntityDamageByEntityEvent) {
+ if (!event.entity.hasMetadata("ArenaSpawn"))
+ return
+
+ // This cast should be safe since we should be the only ones editing this.
+ val spawnName = event.entity.getMetadata("ArenaSpawn")[0].value() as String
+
+ event.isCancelled = true
+
+ editManager.removeSpawnGUI(player, spawnName)
+ }
+
+ @EventHandler
+ fun onPlayerInteractAtEntity(event: PlayerInteractAtEntityEvent) {
+ if (!editManager.shouldProcessEvent(event.player, EditSetting.ALWAYS)) {
+ return
+ }
+
+ if (event.rightClicked is ArmorStand) {
+ playerInteractArmorstand(event.player, event)
+ }
+ }
+
+ private fun playerInteractArmorstand(player: Player, event: PlayerInteractAtEntityEvent) {
+ if (!event.rightClicked.hasMetadata("ArenaSpawn"))
+ return
+
+ // This cast should be safe since we should be the only ones editing this.
+ val spawnName = event.rightClicked.getMetadata("ArenaSpawn")[0].value() as String
+
+ val spawn = editManager.getSpawn(spawnName)
+
+ event.isCancelled = true
+
+ chestGUI(player, plugin) {
+ title = Component.text("Options for: $spawnName")
+ rows = 3
+
+ //TODO: Any more actions to add to a spawn?
+
+ // 0 1 2 3 4 5 6 7 8
+ // 0 b b b b I b b b b
+ // 1 b x R x x x D x b
+ // 2 b x x x x x x x b
+ // 3 b b b b b b b b b
+
+ border {
+ material = Material.LIGHT_BLUE_STAINED_GLASS_PANE
+ }
+
+ entry {
+ x = 4
+ y = 0
+
+ name = Component.text("Information")
+ material = Material.PAPER
+
+ lore(Component.empty())
+ lore(Component.text("x: ${spawn.x}"))
+ lore(Component.text("y: ${spawn.y}"))
+ lore(Component.text("z: ${spawn.z}"))
+ lore(Component.text("yaw: ${spawn.yaw}"))
+ lore(Component.text("pitch: ${spawn.pitch}"))
+ }
+
+ entry {
+ x = 2
+ y = 1
+
+ name = Component.text("Add to rule")
+ material = Material.LIME_DYE
+
+ onLeftClick {
+ editManager.addSpawnToRuleGUI(player, spawnName)
+ }
+ }
+
+ entry {
+ x = 6
+ y = 1
+
+ name = Component.text("Delete")
+ material = Material.BARRIER
+
+ onLeftClick {
+ editManager.removeSpawnGUI(player, spawnName)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/FallListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/FallListener.kt
new file mode 100644
index 0000000..9db39ff
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/FallListener.kt
@@ -0,0 +1,27 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.entity.EntityDamageEvent
+
+class FallListener(
+ private val arenaManager: ArenaManager
+): Listener {
+
+ @EventHandler
+ fun onDamage(event: EntityDamageEvent) {
+ if (event.entity !is Player)
+ return
+
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.NO_FALL, event.entity as Player))
+ return
+
+ if (event.cause == EntityDamageEvent.DamageCause.FALL
+ || event.cause == EntityDamageEvent.DamageCause.FLY_INTO_WALL
+ )
+ event.isCancelled = true
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/FireballListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/FireballListener.kt
new file mode 100644
index 0000000..4a6d0a8
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/FireballListener.kt
@@ -0,0 +1,80 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.managers.ConfigManager
+import nl.kallestruik.darena.types.ArenaFeature
+import org.bukkit.Material
+import org.bukkit.entity.Fireball
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.block.Action
+import org.bukkit.event.entity.EntityDamageByEntityEvent
+import org.bukkit.event.entity.EntityExplodeEvent
+import org.bukkit.event.player.PlayerInteractEvent
+
+class FireballListener(
+ private val arenaManager: ArenaManager,
+ private val configManager: ConfigManager,
+): Listener {
+
+ @EventHandler
+ fun onUse(event: PlayerInteractEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.ITEM_FIREBALL, event.player))
+ return
+
+ if (event.action != Action.RIGHT_CLICK_AIR && event.action != Action.RIGHT_CLICK_BLOCK)
+ return
+
+ if (!event.hasItem() || event.item!!.type != Material.FIRE_CHARGE)
+ return
+
+ if (event.player.getCooldown(Material.FIRE_CHARGE) > 0) return
+
+ val fireball = event.player.launchProjectile(Fireball::class.java)
+ fireball.velocity = fireball.velocity.multiply(3)
+ fireball.setIsIncendiary(false)
+ fireball.yield = 4f
+
+
+ event.player.setCooldown(Material.FIRE_CHARGE, configManager.cooldownFireball)
+
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onEntityExplode(event: EntityExplodeEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.ITEM_FIREBALL, null))
+ return
+
+ for (block in event.blockList()) {
+ block.type = Material.AIR
+ }
+ }
+
+ @EventHandler
+ fun onDamage(event: EntityDamageByEntityEvent) {
+ if (event.entity !is Player)
+ return
+
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.ITEM_FIREBALL, event.entity as Player))
+ return
+
+ if (event.damager !is Fireball)
+ return
+
+ event.damage = 0.0
+ }
+
+ @EventHandler
+ fun onAttack(event: EntityDamageByEntityEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.ITEM_FIREBALL, null))
+ return
+
+ if (event.entity !is Fireball)
+ return
+
+ event.isCancelled = true
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/GrapplingHookListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/GrapplingHookListener.kt
new file mode 100644
index 0000000..1e1bb96
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/GrapplingHookListener.kt
@@ -0,0 +1,50 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import org.bukkit.Location
+import org.bukkit.Material
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.player.PlayerFishEvent
+
+class GrapplingHookListener(
+ val arenaManager: ArenaManager,
+): Listener {
+
+ @EventHandler
+ fun onFish(event: PlayerFishEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.ITEM_GRAPPLING_HOOK, event.player)) {
+ return
+ }
+
+ if (!isNearBlock(event.hook.location))
+ return
+
+ val direction = event.hook.location.toVector().subtract(event.player.eyeLocation.toVector()).normalize()
+
+ event.player.velocity = event.player.velocity.add(direction.multiply(3))
+ }
+
+ fun isNearBlock(location: Location): Boolean {
+ if (location.subtract(0.0, 1.0, 0.0).block.type != Material.AIR)
+ return true
+
+ if (location.subtract(1.0, 0.0, 0.0).block.type != Material.AIR)
+ return true
+
+ if (location.subtract(0.0, 0.0, 1.0).block.type != Material.AIR)
+ return true
+
+ if (location.add(0.0, 1.0, 0.0).block.type != Material.AIR)
+ return true
+
+ if (location.add(1.0, 0.0, 0.0).block.type != Material.AIR)
+ return true
+
+ if (location.add(0.0, 0.0, 1.0).block.type != Material.AIR)
+ return true
+
+ return false
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/KillListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/KillListener.kt
new file mode 100644
index 0000000..36d8804
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/KillListener.kt
@@ -0,0 +1,99 @@
+package nl.kallestruik.darena.listeners
+
+import net.kyori.adventure.text.Component
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.types.ArenaFeature
+import nl.kallestruik.darena.util.ArenaUtil
+import org.bukkit.Bukkit
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.Entity
+import org.bukkit.entity.Player
+import org.bukkit.event.Cancellable
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.entity.EntityDamageByEntityEvent
+import org.bukkit.event.entity.EntityDamageEvent
+import org.bukkit.event.entity.PlayerDeathEvent
+import org.bukkit.event.entity.ProjectileHitEvent
+
+class KillListener(
+ private val arenaManager: ArenaManager
+): Listener {
+
+ @EventHandler
+ fun onDeath(event: PlayerDeathEvent) {
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.ALWAYS, event.entity))
+ return
+
+ event.isCancelled = true
+ event.entity.health = event.entity.getAttribute(Attribute.GENERIC_MAX_HEALTH)?.value ?: 20.0
+
+ arenaManager.currentArena!!.session!!.deaths.add(event.entity.uniqueId)
+
+ val killerUUID = arenaManager.currentArena!!.session!!.lastDamage[event.entity.uniqueId]
+ val killer = if (killerUUID != null)
+ Bukkit.getPlayer(killerUUID)
+ else
+ null
+
+ if (killer != null)
+ arenaManager.currentArena!!.pointsSession!!.giveKillPoints(killer)
+
+ if (arenaManager.shouldProcessEvent(ArenaFeature.DEATH_MESSAGE, event.entity)) {
+ if (killer == null)
+ arenaManager.currentArena!!.sendMessage(
+ Component.text("${event.entity.name} died")
+ )
+ else
+ arenaManager.currentArena!!.sendMessage(
+ Component.text("${event.entity.name} was killed by ${killer.name}")
+ )
+ }
+
+ if (arenaManager.shouldProcessEvent(ArenaFeature.RESPAWN, event.entity)) {
+ arenaManager.currentArena!!.respawn(event.entity)
+ } else {
+ if (arenaManager.shouldProcessEvent(ArenaFeature.ALWAYS, event.entity))
+ arenaManager.currentArena!!.makeSpectator(event.entity)
+ }
+
+ if (arenaManager.currentArena!!.session!!.participants.size <= arenaManager.currentArena!!.config.endConditions.playersLeft) {
+ arenaManager.end()
+ }
+
+ }
+
+ @EventHandler
+ fun onDamage(event: EntityDamageByEntityEvent) {
+ checkAndUpdateDamager(event.entity, event.damager, event)
+ }
+
+ @EventHandler
+ fun onProjectileHit(event: ProjectileHitEvent) {
+ checkAndUpdateDamager(event.hitEntity, event.entity, event)
+ }
+
+ private fun checkAndUpdateDamager(hit: Entity?, hitter: Entity, event: Cancellable) {
+ if (hit !is Player)
+ return
+
+ val damager = ArenaUtil.resolveDamager(hitter) ?: return
+
+ if (!arenaManager.shouldProcessEvent(ArenaFeature.PVP, hit)) {
+ if (arenaManager.shouldProcessEvent(ArenaFeature.ALWAYS, hit)) {
+ if (event is EntityDamageByEntityEvent)
+ if (event.cause != EntityDamageEvent.DamageCause.ENTITY_EXPLOSION
+ && event.cause != EntityDamageEvent.DamageCause.BLOCK_EXPLOSION
+ ) {
+ event.isCancelled = true
+ }
+ }
+ return
+ }
+
+ if (hit.uniqueId == damager.uniqueId)
+ return
+
+ arenaManager.currentArena!!.session!!.lastDamage[hit.uniqueId] = damager.uniqueId
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/SectionListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/SectionListener.kt
new file mode 100644
index 0000000..f1e6107
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/SectionListener.kt
@@ -0,0 +1,35 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.events.CheckpointReachedEvent
+import nl.kallestruik.darena.managers.ArenaManager
+import nl.kallestruik.darena.util.ArenaUtil
+import nl.kallestruik.darena.util.Logger
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+
+class SectionListener(
+ val arenaManager: ArenaManager,
+): Listener {
+
+ @EventHandler
+ fun onCheckpointReached(event: CheckpointReachedEvent) {
+ val checkpoint = event.checkpoint
+ val arena = event.arena
+ val uuid = event.player.uniqueId
+
+ // Increment finished section counter for player.
+ arena.session!!.finishedSectionsByPlayer[uuid] = arena.session!!.finishedSectionsByPlayer.getOrDefault(uuid, 0) + 1
+
+ if (checkpoint.spawnPool != null) {
+ val finishedSectionsByPlayer = arena.session!!.finishedSectionsByPlayer[uuid]!!
+ arena.pointsSession!!.givePoolPoints(event.player, checkpoint.spawnPool, finishedSectionsByPlayer)
+
+ Logger.debug(CheckpointListener::class, "finished count: $finishedSectionsByPlayer")
+ Logger.debug(CheckpointListener::class, "Times: ${checkpoint.spawnPool.times}")
+ if (finishedSectionsByPlayer >= checkpoint.spawnPool.times) {
+ ArenaUtil.finishPlayer(event.player, arena, arenaManager)
+ return
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/listeners/TeamListener.kt b/src/main/kotlin/nl/kallestruik/darena/listeners/TeamListener.kt
new file mode 100644
index 0000000..190363b
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/listeners/TeamListener.kt
@@ -0,0 +1,28 @@
+package nl.kallestruik.darena.listeners
+
+import nl.kallestruik.darena.managers.TeamManager
+import nl.kallestruik.darena.util.Logger
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.player.PlayerJoinEvent
+
+class TeamListener(
+ private val teamManager: TeamManager
+): Listener {
+
+ @EventHandler
+ fun onPlayerJoined(event: PlayerJoinEvent) {
+ var team = teamManager.getTeamForPlayer(event.player.uniqueId)
+ if (team != null) {
+ team.mcTeam.addEntry(event.player.name)
+ return
+ }
+
+ if (!teamManager.autoTeams)
+ return
+
+ Logger.info(TeamListener::class, "Creating automatic team for player: ${event.player.name}")
+ team = teamManager.createTeam(event.player.name)
+ teamManager.addPlayerToTeam(team, event.player.uniqueId)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/managers/ArenaManager.kt b/src/main/kotlin/nl/kallestruik/darena/managers/ArenaManager.kt
index df3d8a4..90d6cd2 100644
--- a/src/main/kotlin/nl/kallestruik/darena/managers/ArenaManager.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/managers/ArenaManager.kt
@@ -1,27 +1,144 @@
package nl.kallestruik.darena.managers
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
import nl.kallestruik.darena.arenas.Arena
import nl.kallestruik.darena.arenas.ArenaConfig
-import nl.kallestruik.darena.util.ArenaUtil
+import nl.kallestruik.darena.exceptions.ArenaCreationException
+import nl.kallestruik.darena.exceptions.ArenaEndAbortedException
+import nl.kallestruik.darena.exceptions.ArenaStartAbortedException
+import nl.kallestruik.darena.types.ArenaFeature
+import nl.kallestruik.darena.types.Reloadable
+import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.dlib.config.ConfigHelper.toConfigSchema
+import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import java.io.File
import java.nio.file.Files
class ArenaManager(
- private val arenaUtil: ArenaUtil,
+ private val teamManager: TeamManager,
+ private val pointsManager: PointsManager,
+ private val configManager: ConfigManager,
private val plugin: JavaPlugin,
-) {
- private val arenas: MutableList = ArrayList()
+ private val arenaFolder: File,
+ schemaFile: File,
+): Reloadable {
+ lateinit var editManager: EditManager;
+ private val arenas: MutableMap = mutableMapOf()
+ var currentArena: Arena? = null
- fun loadArenas(arenaFolder: File) {
+ override fun reload() {
+ arenas.clear()
+ loadArenas()
+ }
+
+ init {
+ schemaFile.parentFile.mkdirs()
+ schemaFile.writeText(Json.encodeToString(ArenaConfig.serializer().descriptor.toConfigSchema(listOf())))
+ }
+
+ fun loadArenas() {
+ Logger.trace(ArenaManager::class, "loadArenas()")
+ arenaFolder.mkdirs()
Files.walk(arenaFolder.toPath()).use { walk ->
walk.forEach { path ->
- arenas.add(Arena(
- ArenaConfig.load(path.toFile()),
- arenaUtil,
- plugin)
+ if (Files.isDirectory(path))
+ return@forEach
+
+ if (path.toString().endsWith(".yml")) {
+ Logger.info(ArenaManager::class, "Skipping legacy config at $path")
+ return@forEach
+ }
+
+ Logger.info(ArenaManager::class, "Loading arena from: $path")
+ val config = ArenaConfig.load(path.toFile())
+
+ arenas[config.name] = Arena(
+ config,
+ plugin,
+ configManager,
+ this,
+ teamManager,
+ pointsManager,
)
}
}
}
+
+ @Throws(ArenaStartAbortedException::class)
+ fun start(arena: Arena, loadingTimeOverride: Int, spectatorTimeOverride: Int, beforeStartTimeOverride: Int) {
+ Logger.trace(ArenaManager::class, "start(arena: ${arena.config.name}, loadingTimeOverride: $loadingTimeOverride, spectatorTimeOverride: $spectatorTimeOverride, beforeStartTimeOverride: $beforeStartTimeOverride)")
+
+ if (currentArena != null)
+ throw ArenaStartAbortedException("There is currently another arena in progress. Please end that one first.")
+
+ if (editManager.isEditing())
+ throw ArenaStartAbortedException("There is currently an arena being edited. Please finish that first.")
+
+ // Try to start the arena. If something goes wrong clean it up and throw the exception again so the user can be informed.
+ arena.start(loadingTimeOverride, spectatorTimeOverride, beforeStartTimeOverride)
+
+ currentArena = arena
+ }
+
+ @Throws(ArenaEndAbortedException::class)
+ fun end() {
+ Logger.trace(ArenaManager::class, "end()")
+ if (currentArena == null)
+ throw ArenaEndAbortedException("There is currently no arena in progress.")
+
+ currentArena!!.end()
+ currentArena = null
+ }
+
+ @Throws(ArenaCreationException::class)
+ fun createArena(name: String) {
+ if (arenas.contains(name))
+ throw ArenaCreationException("Arena with name $name already exists!")
+
+ val config = ArenaConfig(name = name)
+ config.file = File(arenaFolder, "$name.json")
+
+ arenas[config.name] = Arena(
+ config,
+ plugin,
+ configManager,
+ this,
+ teamManager,
+ pointsManager,
+ )
+
+ config.save(false)
+ }
+
+ fun saveConfig(arena: Arena) {
+ arena.config.save(false)
+ }
+
+ fun getArena(name: String): Arena? {
+ Logger.trace(ArenaManager::class, "getArena(name: $name)")
+ return arenas[name]
+ }
+
+ fun getAllArenaNames(): Collection {
+ Logger.trace(ArenaManager::class, "getAllArenaNames()")
+ return arenas.keys
+ }
+
+ fun shouldProcessEvent(feature: ArenaFeature, player: Player?): Boolean {
+ if (currentArena == null)
+ return false
+
+ if (!currentArena!!.session!!.isInProgress)
+ return false
+
+ if (feature != ArenaFeature.ALWAYS && !currentArena!!.hasFeature(feature))
+ return false
+
+ if ((player != null && !currentArena!!.session!!.participants.contains(player)))
+ return false
+
+ return true
+ }
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/managers/ConfigManager.kt b/src/main/kotlin/nl/kallestruik/darena/managers/ConfigManager.kt
index 022a711..8ddf605 100644
--- a/src/main/kotlin/nl/kallestruik/darena/managers/ConfigManager.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/managers/ConfigManager.kt
@@ -1,12 +1,32 @@
package nl.kallestruik.darena.managers
+import nl.kallestruik.darena.types.Reloadable
+import nl.kallestruik.darena.types.arena.ArenaLocation
+import nl.kallestruik.darena.util.ConfigHelper
import java.io.File
class ConfigManager(
- val configFile: File
-) {
+ val configFile: File,
+): Reloadable {
+ var spawnPos: ArenaLocation = ArenaLocation(0, 100, 0)
+ var spawnWorldName = "world"
+ var cooldownFireball = 30
+
+
+ override fun reload() {
+ load()
+ }
fun load() {
- //TODO: Load config
+ val config = ConfigHelper.getOrCreateConfig(configFile, "template/config.yml")
+
+ if (config.isConfigurationSection("spawn-pos"))
+ spawnPos = ArenaLocation.load(config.getConfigurationSection("spawn-pos")!!)
+
+ if (config.contains("spawn-world"))
+ spawnWorldName = config.getString("spawn-world")!!
+
+ if (config.contains("cooldown-fireball"))
+ cooldownFireball = config.getInt("cooldown-fireball")
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/managers/EditManager.kt b/src/main/kotlin/nl/kallestruik/darena/managers/EditManager.kt
new file mode 100644
index 0000000..20b84ea
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/managers/EditManager.kt
@@ -0,0 +1,311 @@
+package nl.kallestruik.darena.managers
+
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.NamedTextColor
+import nl.kallestruik.darena.arenas.Arena
+import nl.kallestruik.darena.arenas.world.ArenaWorld
+import nl.kallestruik.darena.exceptions.ArenaEditException
+import nl.kallestruik.darena.types.EditSetting
+import nl.kallestruik.darena.types.EditVisualization
+import nl.kallestruik.darena.types.arena.ArenaSpawn
+import nl.kallestruik.darena.types.arena.ArenaSpawnRule
+import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.darena.util.RenderUtil
+import nl.kallestruik.dlib.config.ConfigEntryInfo
+import nl.kallestruik.dlib.config.ConfigHelper
+import nl.kallestruik.dlib.gui.confirmationDialog
+import nl.kallestruik.dlib.gui.textInputDialog
+import org.bukkit.Color
+import org.bukkit.GameMode
+import org.bukkit.Location
+import org.bukkit.Material
+import org.bukkit.entity.EntityType
+import org.bukkit.entity.Player
+import org.bukkit.metadata.FixedMetadataValue
+import org.bukkit.plugin.Plugin
+import org.bukkit.scheduler.BukkitRunnable
+import org.bukkit.scheduler.BukkitTask
+import java.util.*
+
+class EditManager(
+ private val arenaManager: ArenaManager,
+ private val plugin: Plugin
+) {
+ private var currentArena: Arena? = null
+ private var currentWorld: ArenaWorld? = null
+ private val currentSettings: MutableMap> = mutableMapOf()
+ private val currentVisualizations: MutableMap = mutableMapOf()
+ private var checkpointVisualizationTask: BukkitTask? = null
+
+ @Throws(ArenaEditException::class)
+ fun load(arena: Arena) {
+ Logger.trace(EditManager::class, "load(arena: ${arena.config.name})")
+
+ if (isEditing())
+ throw ArenaEditException("Cant start editing a world when another one is already loaded!")
+
+ if (arenaManager.currentArena != null)
+ throw ArenaEditException("Cant start editing a world when there is an arena in progress!")
+
+ arena.world.reset()
+ currentArena = arena
+ currentWorld = arena.world
+ }
+
+ @Throws(ArenaEditException::class)
+ fun save() {
+ Logger.trace(EditManager::class, "save()")
+
+ if (!isEditing())
+ throw ArenaEditException("Cant save a world if you aren't editing one!")
+
+ if (isVisualizationEnabled(EditVisualization.SPAWNS)) {
+ removeSpawnVisualizers()
+ }
+
+ currentWorld!!.save()
+ saveConfig()
+
+ if (isVisualizationEnabled(EditVisualization.SPAWNS)) {
+ addSpawnVisualizers()
+ }
+ }
+
+ @Throws(ArenaEditException::class)
+ fun tp(player: Player) {
+ Logger.trace(EditManager::class, "tp(player: ${player.name})")
+
+ if (!isEditing())
+ throw ArenaEditException("Cant teleport to a world if none is being edited!")
+
+ player.teleport(Location(currentWorld!!.world, 0.5, 100.0, 0.5))
+ player.gameMode = GameMode.CREATIVE
+ }
+
+ fun finish() {
+ Logger.trace(EditManager::class, "finish()")
+
+ if (!isEditing())
+ throw ArenaEditException("Cant stop editing what is not being edited!")
+
+ save()
+ currentWorld!!.empty()
+ currentArena = null
+ currentWorld = null
+ currentSettings.clear()
+ checkpointVisualizationTask?.cancel()
+ }
+
+ fun isEditing(): Boolean {
+ return currentWorld != null
+ }
+
+// fun getConfigValue(path: String): ConfigEntryInfo {
+// Logger.trace(EditManager::class, "getConfigValue(path: $path)")
+//
+// if (!isEditing())
+// throw ArenaEditException("You arent editing anything!")
+//
+// return ConfigHelper.getInfoByPath(path, currentArena!!.config)
+// }
+
+ fun toggleFeature(player: Player, setting: EditSetting): Boolean {
+ val settingsMap = currentSettings.getOrPut(player.uniqueId, ::mutableMapOf)
+ val newValue = !(settingsMap[setting] ?: false)
+ settingsMap[setting] = newValue
+ return newValue
+ }
+
+ fun saveConfig() {
+ if (!isEditing()) {
+ throw ArenaEditException("You cant save something that has not been changed!")
+ }
+
+ arenaManager.saveConfig(currentArena!!)
+ }
+
+ fun shouldProcessEvent(player: Player?, setting: EditSetting): Boolean {
+ if (currentArena == null)
+ return false
+
+ if (!isEditing())
+ return false
+
+ if (player == null)
+ return false
+
+ if (setting != EditSetting.ALWAYS && !playerHasFeatureEnabled(player, setting))
+ return false
+
+ return true
+ }
+
+ private fun playerHasFeatureEnabled(player: Player, setting: EditSetting): Boolean {
+ return currentSettings[player.uniqueId]?.get(setting) ?: false
+ }
+
+ fun addSpawn(name: String, spawn: ArenaSpawn): Boolean {
+ Logger.trace(EditManager::class, "finishSpawn(name: $name, spawn: $spawn)")
+
+ if (!isEditing())
+ return false
+
+ if (name in currentArena!!.config.spawns)
+ return false
+
+ currentArena!!.config.spawns[name] = spawn
+ refreshSpawnVisualizer()
+ return true
+ }
+
+ fun isVisualizationEnabled(visualization: EditVisualization): Boolean {
+ return currentVisualizations[visualization] ?: false
+ }
+
+ fun toggleSpawnVisibility() {
+ if (!isEditing()) {
+ throw ArenaEditException("You cant visualize what isn't there!")
+ }
+
+ val newValue = !isVisualizationEnabled(EditVisualization.SPAWNS)
+ currentVisualizations[EditVisualization.SPAWNS] = newValue
+
+ if (newValue) {
+ addSpawnVisualizers()
+ } else {
+ removeSpawnVisualizers()
+ }
+
+ }
+
+ private fun addSpawnVisualizers() {
+ val world = currentWorld!!.world
+ for (entry in currentArena!!.config.spawns) {
+ val entity = world.spawnEntity(entry.value.toLocation(world), EntityType.ARMOR_STAND)
+ entity.setGravity(false)
+ entity.isCustomNameVisible = true
+ entity.customName(Component.text("SPAWN: ${entry.key}"))
+ entity.setMetadata("ArenaSpawn", FixedMetadataValue(plugin, entry.key))
+ }
+ }
+
+ private fun removeSpawnVisualizers() {
+ for (entity in currentWorld!!.world.entities) {
+ if (!entity.hasMetadata("ArenaSpawn"))
+ continue
+
+ entity.remove()
+ }
+ }
+
+ fun refreshSpawnVisualizer() {
+ if (!isEditing())
+ return
+
+ if (!isVisualizationEnabled(EditVisualization.SPAWNS))
+ return
+
+ removeSpawnVisualizers()
+ addSpawnVisualizers()
+ }
+
+ fun removeSpawn(spawnName: String) {
+ if (!isEditing()) {
+ throw ArenaEditException("Cant remove a spawn if you arent editing an arena!")
+ }
+
+ currentArena!!.config.spawns.remove(spawnName)
+ refreshSpawnVisualizer()
+ }
+
+ fun toggleCheckpointVisibility() {
+ if (!isEditing()) {
+ throw ArenaEditException("You cant visualize what isn't there!")
+ }
+
+ val newValue = !isVisualizationEnabled(EditVisualization.CHECKPOINTS)
+ currentVisualizations[EditVisualization.CHECKPOINTS] = newValue
+ if (newValue) {
+ checkpointVisualizationTask = CheckpointVisualizationRunnable(currentArena!!).runTaskTimer(plugin, 0, 5)
+ } else {
+ checkpointVisualizationTask?.cancel()
+ }
+ }
+
+ fun addSpawnToRuleGUI(player: Player, spawnName: String) {
+ if (!isEditing())
+ throw ArenaEditException("You arent editing an arena!")
+
+ if (spawnName !in currentArena!!.config.spawns)
+ throw ArenaEditException("That spawn does not exist!")
+
+ textInputDialog(player, plugin) {
+ prompt = Component.text("Rule name")
+ item {
+ material = Material.LIME_DYE
+ }
+
+ onCancel {
+ player.sendMessage(Component.text("Canceled adding $spawnName to a rule."))
+ }
+
+ onFinish { name ->
+ val rule: ArenaSpawnRule
+ if (name in currentArena!!.config.spawnRules) {
+ rule = currentArena!!.config.spawnRules[name]!!
+
+ player.sendMessage(Component.text("Added $spawnName to rule $name."))
+ } else {
+ rule = ArenaSpawnRule()
+
+ player.sendMessage(Component.text("Created new rule $name and added $spawnName to it."))
+ }
+
+ rule.spawns.add(spawnName)
+ currentArena!!.config.spawnRules[name] = rule
+ }
+ }
+ }
+
+ fun getSpawn(spawnName: String): ArenaSpawn {
+ if (!isEditing())
+ throw ArenaEditException("You cant get a spawn when you arent editing an arena!")
+
+ return currentArena!!.config.spawns[spawnName]
+ ?: throw ArenaEditException("That spawn does not exist!")
+ }
+
+ fun removeSpawnGUI(player: Player, spawnName: String) {
+ confirmationDialog(player, plugin) {
+ title = Component.text("Remove spawn?")
+ description = Component
+ .text("Are you sure that you want to remove this spawn?")
+ .color(NamedTextColor.RED)
+
+ onConfirm {
+ removeSpawn(spawnName)
+ refreshSpawnVisualizer()
+ player.sendMessage(Component.text("Spawn $spawnName removed!"))
+ }
+ }
+ }
+
+ private class CheckpointVisualizationRunnable(
+ val arena: Arena,
+ ): BukkitRunnable() {
+ override fun run() {
+ for (checkpoint in arena.config.checkpoints) {
+ val lower = checkpoint.lower
+ val upper = checkpoint.upper
+
+ RenderUtil.drawAABB(
+ arena.world.world,
+ lower.x, lower.y, lower.z,
+ upper.x, upper.y, upper.z,
+ Color.AQUA
+ )
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/managers/GameManager.kt b/src/main/kotlin/nl/kallestruik/darena/managers/GameManager.kt
new file mode 100644
index 0000000..3b62021
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/managers/GameManager.kt
@@ -0,0 +1,41 @@
+package nl.kallestruik.darena.managers
+
+import nl.kallestruik.darena.types.Game
+import nl.kallestruik.darena.types.Reloadable
+import nl.kallestruik.darena.util.ConfigHelper
+import nl.kallestruik.darena.util.Logger
+import java.io.File
+
+class GameManager(
+ val arenaManager: ArenaManager,
+ val gameFile: File,
+): Reloadable {
+ val games = mutableMapOf()
+
+ fun load() {
+ val config = ConfigHelper.getOrCreateConfig(gameFile, "template/games.yml")
+
+ for (gameName in config.getKeys(false)) {
+ val game = Game(gameName)
+ for (arenaName in config.getStringList("$gameName.arenas")) {
+ val arena = arenaManager.getArena(arenaName)
+ if (arena == null) {
+ Logger.warn(GameManager::class, "Arena with name $arenaName is used in games.yml but not defined!")
+ continue
+ }
+
+ game.arenas.add(arena)
+ }
+
+ if (config.contains("$gameName.description"))
+ game.description = config.getString("$gameName.description")!!
+
+ games[gameName] = game
+ }
+ }
+
+ override fun reload() {
+ games.clear()
+ load()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/managers/PointsManager.kt b/src/main/kotlin/nl/kallestruik/darena/managers/PointsManager.kt
index 62b732c..f3d8d2f 100644
--- a/src/main/kotlin/nl/kallestruik/darena/managers/PointsManager.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/managers/PointsManager.kt
@@ -1,10 +1,16 @@
package nl.kallestruik.darena.managers
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.NamedTextColor
import nl.kallestruik.darena.exceptions.TeamNotFoundException
import nl.kallestruik.darena.types.Team
+import nl.kallestruik.darena.util.ConfigHelper
+import nl.kallestruik.darena.util.Logger
+import org.bukkit.Bukkit
+import org.bukkit.command.CommandSender
+import org.bukkit.configuration.file.YamlConfiguration
import java.io.File
import java.util.*
-import kotlin.collections.HashMap
class PointsManager(
val teamManager: TeamManager,
@@ -13,24 +19,59 @@ class PointsManager(
private val points: MutableMap = HashMap()
fun load() {
- //TODO: Load points from file.
+ val config = ConfigHelper.getOrCreateConfig(pointsFile, "template/points.yml")
+ if (config.isConfigurationSection("teams")) {
+ for (teamName in config.getConfigurationSection("teams")!!.getKeys(false)) {
+ try {
+ val team = teamManager.getTeamByName(teamName)
+ points[team] = config.getInt("teams.$teamName")
+ } catch (e: TeamNotFoundException) {
+ Logger.warn(PointsManager::class, "Team $teamName is present in points.yml but does not exist ignoring.")
+ }
+ }
+ }
+ updateTeamSuffixForAll()
+ }
+
+ fun save() {
+ val config = YamlConfiguration()
+ val teamsSection = config.createSection("teams")
+ for (entry in points) {
+ teamsSection.set(entry.key.name, entry.value)
+ }
+
+ config.save(pointsFile)
}
@Throws(TeamNotFoundException::class)
- fun givePointsToPlayer(uuid: UUID, amount: Int, reason: String) {
- val team = teamManager.getTeamForPlayer(uuid)
+ fun givePointsToPlayer(uuid: UUID, amount: Int) {
+ val team = teamManager.getTeamForPlayer(uuid) ?: throw TeamNotFoundException()
- givePointsToTeam(team, amount, reason)
+ givePointsToTeam(team, amount)
}
- fun givePointsToTeam(team: Team, amount: Int, reason: String) {
- points[team] = (points[team]?: 0) + amount
- //TODO: Send message with reason to team members (and everyone else?)
+ fun givePointsToTeam(team: Team, amount: Int) {
+ points[team] = (points[team] ?: 0) + amount
+ updateTeamSuffix(team)
+ }
+
+ fun updateTeamSuffix(team: Team) {
+ team.mcTeam.suffix(
+ Component
+ .text(" [${getPointsForTeam(team)}]")
+ .color(NamedTextColor.AQUA)
+ )
+ }
+
+ fun updateTeamSuffixForAll() {
+ for (team in teamManager.getAllTeams()) {
+ updateTeamSuffix(team)
+ }
}
@Throws(TeamNotFoundException::class)
fun getPointsForPlayer(uuid: UUID): Int {
- val team = teamManager.getTeamForPlayer(uuid)
+ val team = teamManager.getTeamForPlayer(uuid) ?: throw TeamNotFoundException()
return getPointsForTeam(team)
}
@@ -39,5 +80,31 @@ class PointsManager(
return points[team] ?: 0
}
+ fun sendLeaderboard(sender: CommandSender) {
+ val sortedMap = points.toList().sortedByDescending { (_, v) -> v }.toMap()
+ //TODO: Make these message configurable
+ sender.sendMessage(Component.text("============[ Leaderboard ]============"))
+ var i = 1
+ for (entry in sortedMap) {
+ var teamMembers: Component = Component.empty()
+ for (uuid in teamManager.getTeamMembers(entry.key)) {
+ val player = Bukkit.getPlayer(uuid)
+ teamMembers = teamMembers.append(Component.text("${player?.name}\n"))
+ }
+ sender.sendMessage(
+ Component.empty()
+ .append(Component.text("$i. "))
+ .append(teamManager.getTeamComponent(entry.key))
+ .append(Component.text(": "))
+ .append(Component.text(entry.value))
+ )
+ i++
+ }
+ }
+
+ fun clear() {
+ points.clear()
+ updateTeamSuffixForAll()
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/managers/TeamManager.kt b/src/main/kotlin/nl/kallestruik/darena/managers/TeamManager.kt
index eccd6f4..1c51c5e 100644
--- a/src/main/kotlin/nl/kallestruik/darena/managers/TeamManager.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/managers/TeamManager.kt
@@ -1,13 +1,16 @@
package nl.kallestruik.darena.managers
-import net.kyori.adventure.text.format.TextColor
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.event.HoverEvent
import nl.kallestruik.darena.exceptions.TeamExistsException
import nl.kallestruik.darena.exceptions.TeamNotFoundException
import nl.kallestruik.darena.types.Team
+import nl.kallestruik.darena.util.ConfigHelper
+import nl.kallestruik.darena.util.Logger
+import org.bukkit.Bukkit
+import org.bukkit.configuration.file.YamlConfiguration
import java.io.File
import java.util.*
-import kotlin.collections.ArrayList
-import kotlin.collections.HashMap
class TeamManager(
val teamsFile: File
@@ -15,37 +18,91 @@ class TeamManager(
private val teams: MutableMap = HashMap()
private val teamMembers: MutableMap> = HashMap()
private val teamForPlayer: MutableMap = HashMap()
+ var autoTeams: Boolean = true
fun load() {
- //TODO: Load teams from file
+ // Make sure all lists are empty so we don't have leftovers.
+ teams.clear()
+ teamMembers.clear()
+ teamForPlayer.clear()
+
+ val config = ConfigHelper.getOrCreateConfig(teamsFile, "template/teams.yml")
+ autoTeams = config.getBoolean("auto-teams")
+ if (config.isConfigurationSection("teams")) {
+ for (teamName in config.getConfigurationSection("teams")!!.getKeys(false)) {
+ Logger.debug(TeamManager::class, "Loading team $teamName")
+ val team = Team.fromConfig(config.getConfigurationSection("teams.$teamName")!!)
+ teams[teamName] = team
+ }
+ }
+
+ if (config.contains("members")) {
+ for (memberString in config.getStringList("members")) {
+ val parts = memberString.split(":")
+ val uuid = UUID.fromString(parts[0])
+ val teamName = parts[1]
+ addPlayerToTeam(teamName, uuid)
+ }
+ }
+ }
+
+ fun save() {
+ val config = YamlConfiguration()
+ config.set("auto-teams", autoTeams)
+
+ val teamSection = config.createSection("teams")
+ for (team in teams.values) {
+ team.save(teamSection)
+ }
+
+ config.set("members", teamForPlayer.map {
+ "${it.key}:${it.value.name}"
+ })
+
+ config.save(teamsFile)
}
@Throws(TeamExistsException::class)
- fun createTeam(teamName: String) {
+ fun createTeam(teamName: String): Team {
if (teams.containsKey(teamName))
throw TeamExistsException("The team $teamName already exists!")
- teams[teamName] = Team(teamName)
+ val team = Team(teamName)
+ teams[teamName] = team
+ return team
}
- @Throws(TeamNotFoundException::class)
- fun setTeamColor(teamName: String, color: TextColor) {
- val team = getTeamByName(teamName)
-
- team.color = color
+ fun removeTeam(team: Team) {
+ teams.remove(team.name)
+ for (entry in teamForPlayer) {
+ if (entry.value == team) {
+ removePlayerFromTeam(entry.key)
+ }
+ }
+ team.mcTeam.unregister()
}
@Throws(TeamNotFoundException::class)
fun addPlayerToTeam(teamName: String, uuid: UUID) {
+ Logger.trace(TeamManager::class, "addPlayerToTeam(teamName: $teamName, uuid: $uuid)")
val team = getTeamByName(teamName)
+ addPlayerToTeam(team, uuid)
+ }
+
+ fun addPlayerToTeam(team: Team, uuid: UUID) {
+ Logger.trace(TeamManager::class, "addPlayerToTeam(team: ${team.name}, uuid: $uuid)")
teamMembers.getOrPut(team) { ArrayList() }.add(uuid)
teamForPlayer[uuid] = team
+
+ val player = Bukkit.getPlayer(uuid)
+ if (player != null)
+ team.mcTeam.addEntry(player.name)
}
@Throws(TeamNotFoundException::class)
- fun getTeamForPlayer(uuid: UUID): Team {
- return teamForPlayer[uuid] ?: throw TeamNotFoundException("Player with uuid $uuid is not part of a team!")
+ fun getTeamForPlayer(uuid: UUID): Team? {
+ return teamForPlayer[uuid]
}
@Throws(TeamNotFoundException::class)
@@ -56,6 +113,51 @@ class TeamManager(
@Throws(TeamNotFoundException::class)
fun getTeamMembers(teamName: String): List {
val team = getTeamByName(teamName)
+ return getTeamMembers(team)
+ }
+
+ fun getTeamMembers(team: Team): List {
return teamMembers[team] ?: return emptyList()
}
+
+ fun removeAllTeams(): Int {
+ val count = teams.size
+ for (team in getAllTeams()) {
+ removeTeam(team)
+ }
+ return count
+ }
+
+ fun autoTeam() {
+ for (player in Bukkit.getOnlinePlayers()) {
+ val team = createTeam(player.name)
+ addPlayerToTeam(team, player.uniqueId)
+ }
+ }
+
+ fun removePlayerFromTeam(uuid: UUID) {
+ val team = getTeamForPlayer(uuid)
+ teamMembers[team]?.remove(uuid)
+ teamForPlayer.remove(uuid)
+ team?.mcTeam?.removeEntry(Bukkit.getPlayer(uuid)!!.name)
+ }
+
+ fun getAllTeamNames() = teams.keys
+
+ fun getAllTeams() = teams.values
+
+ fun getMemberTooltip(team: Team): HoverEvent {
+ var hoverComponent = Component.text("Members:")
+ getTeamMembers(team).forEach {
+ hoverComponent = hoverComponent.append(Component.text("\n${Bukkit.getOfflinePlayer(it).name}"))
+ }
+ return hoverComponent.asHoverEvent()
+ }
+
+ fun getTeamComponent(team: Team): Component {
+ return Component
+ .text(team.name)
+ .color(team.color)
+ .hoverEvent(getMemberTooltip(team))
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/ArenaFeature.kt b/src/main/kotlin/nl/kallestruik/darena/types/ArenaFeature.kt
new file mode 100644
index 0000000..0774e10
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/ArenaFeature.kt
@@ -0,0 +1,19 @@
+package nl.kallestruik.darena.types
+
+enum class ArenaFeature {
+ ALWAYS,
+ RESPAWN,
+ DEATH_MESSAGE,
+ CHECKPOINTS,
+ CHECKPOINT_SOUND,
+ CHECKPOINT_MESSAGE,
+ PVP,
+ NO_FALL,
+ SHOW_TIMER,
+ RESTRICT_BUILDING,
+ GAMEMODE_ADVENTURE,
+ HORIZONTAL_BORDERS,
+ NO_TILE_DROPS,
+ ITEM_FIREBALL,
+ ITEM_GRAPPLING_HOOK;
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/ConfigListLoadable.kt b/src/main/kotlin/nl/kallestruik/darena/types/ConfigListLoadable.kt
deleted file mode 100644
index 6a8e656..0000000
--- a/src/main/kotlin/nl/kallestruik/darena/types/ConfigListLoadable.kt
+++ /dev/null
@@ -1,4 +0,0 @@
-package nl.kallestruik.darena.types
-
-interface ConfigListLoadable {
- fun loadFromList(key: String, value: Any): T
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/ConfigListSaveable.kt b/src/main/kotlin/nl/kallestruik/darena/types/ConfigListSaveable.kt
deleted file mode 100644
index 4ec9edc..0000000
--- a/src/main/kotlin/nl/kallestruik/darena/types/ConfigListSaveable.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package nl.kallestruik.darena.types
-
-interface ConfigListSaveable {
- fun getKey(): String
-
- fun getValue(): Any
-}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/ConfigLoadable.kt b/src/main/kotlin/nl/kallestruik/darena/types/ConfigLoadable.kt
deleted file mode 100644
index 73a4fc8..0000000
--- a/src/main/kotlin/nl/kallestruik/darena/types/ConfigLoadable.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package nl.kallestruik.darena.types
-
-import org.bukkit.configuration.ConfigurationSection
-
-interface ConfigLoadable {
- fun load(section: ConfigurationSection): T
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/ConfigSaveable.kt b/src/main/kotlin/nl/kallestruik/darena/types/ConfigSaveable.kt
deleted file mode 100644
index 0b37d32..0000000
--- a/src/main/kotlin/nl/kallestruik/darena/types/ConfigSaveable.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package nl.kallestruik.darena.types
-
-import org.bukkit.configuration.ConfigurationSection
-
-interface ConfigSaveable {
- fun save(section: ConfigurationSection)
-}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/EditSetting.kt b/src/main/kotlin/nl/kallestruik/darena/types/EditSetting.kt
new file mode 100644
index 0000000..c89c006
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/EditSetting.kt
@@ -0,0 +1,6 @@
+package nl.kallestruik.darena.types
+
+enum class EditSetting {
+ ALWAYS,
+ SPAWN_PLACING
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/EditVisualization.kt b/src/main/kotlin/nl/kallestruik/darena/types/EditVisualization.kt
new file mode 100644
index 0000000..198b8e7
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/EditVisualization.kt
@@ -0,0 +1,6 @@
+package nl.kallestruik.darena.types
+
+enum class EditVisualization {
+ SPAWNS,
+ CHECKPOINTS
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/Game.kt b/src/main/kotlin/nl/kallestruik/darena/types/Game.kt
new file mode 100644
index 0000000..eed83a7
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/Game.kt
@@ -0,0 +1,9 @@
+package nl.kallestruik.darena.types
+
+import nl.kallestruik.darena.arenas.Arena
+
+data class Game(
+ var name: String,
+ val arenas: MutableList = mutableListOf(),
+ var description: String = "",
+)
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/Reloadable.kt b/src/main/kotlin/nl/kallestruik/darena/types/Reloadable.kt
new file mode 100644
index 0000000..e3ce336
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/Reloadable.kt
@@ -0,0 +1,6 @@
+package nl.kallestruik.darena.types
+
+interface Reloadable {
+
+ fun reload()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/Team.kt b/src/main/kotlin/nl/kallestruik/darena/types/Team.kt
index fd193f5..2b47a13 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/Team.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/Team.kt
@@ -1,8 +1,47 @@
package nl.kallestruik.darena.types
+import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.TextColor
+import org.bukkit.Bukkit
+import org.bukkit.configuration.ConfigurationSection
data class Team(
- val name: String,
+ val name: String
+) {
+ val mcTeam: org.bukkit.scoreboard.Team = Bukkit.getScoreboardManager().mainScoreboard.getTeam(name)
+ ?: Bukkit.getScoreboardManager().mainScoreboard.registerNewTeam(name)
+
+ init {
+ mcTeam.setOption(org.bukkit.scoreboard.Team.Option.COLLISION_RULE, org.bukkit.scoreboard.Team.OptionStatus.NEVER)
+ }
+
var color: TextColor = TextColor.color(0xFFFFFF)
-)
\ No newline at end of file
+ set(value) {
+ field = value
+ mcTeam.prefix(mcTeam.prefix().color(value))
+ }
+ var prefix: String = ""
+ set(value) {
+ field = value
+ mcTeam.prefix(Component.text("$value ").color(color))
+ }
+
+ fun save(section: ConfigurationSection) {
+ val teamSection = section.createSection(name)
+ teamSection.set("color", color.asHexString())
+ teamSection.set("prefix", prefix)
+ }
+
+ companion object {
+ fun fromConfig(section: ConfigurationSection): Team {
+ val team = Team(
+ section.name
+ )
+ team.color = TextColor.fromCSSHexString(section.getString("color")!!)!!
+ team.prefix = section.getString("prefix")!!
+
+ return team
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaAllowList.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaAllowList.kt
new file mode 100644
index 0000000..cb4bd82
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaAllowList.kt
@@ -0,0 +1,27 @@
+package nl.kallestruik.darena.types.arena
+
+import kotlinx.serialization.Serializable
+import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.dlib.config.annotations.Description
+import org.bukkit.Material
+
+@Serializable
+data class ArenaAllowList(
+ @Description([
+ "The list of rules. These rules support full regular expression. For example .*_wool will match all colors of wool."])
+ var ruleList: MutableList = mutableListOf(),
+) {
+ fun isAllowed(material: Material): Boolean {
+ Logger.trace(ArenaAllowList::class, "isAllowed(material: $material)")
+ for (rule in ruleList) {
+ Logger.debug(ArenaAllowList::class, "Checking rule: $rule")
+ return when (rule.match(material.name.lowercase())) {
+ AllowRuleResult.NO_MATCH -> continue
+ AllowRuleResult.ALLOWED -> true
+ AllowRuleResult.BLOCKED -> false
+ }
+ }
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaAllowRule.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaAllowRule.kt
new file mode 100644
index 0000000..af5170f
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaAllowRule.kt
@@ -0,0 +1,55 @@
+package nl.kallestruik.darena.types.arena
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.dlib.config.annotations.DefaultString
+
+@Serializable
+data class ArenaAllowRule(
+ @Serializable(RegexSerializer::class)
+ var rule: Regex,
+ @DefaultString("BLOCK")
+ var type: Type,
+) {
+ fun match(toMatch: String): AllowRuleResult {
+ Logger.trace(ArenaAllowRule::class, "match(toMatch: $toMatch)")
+ if (rule.matches(toMatch)) {
+ Logger.debug(ArenaAllowRule::class, "Rule matches")
+ return when (type) {
+ Type.ALLOW -> AllowRuleResult.ALLOWED
+ Type.BLOCK -> AllowRuleResult.BLOCKED
+ }
+ }
+
+ return AllowRuleResult.NO_MATCH
+ }
+
+ object RegexSerializer: KSerializer {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Regex", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: Regex) {
+ encoder.encodeString(value.pattern)
+ }
+
+ override fun deserialize(decoder: Decoder): Regex {
+ return Regex(decoder.decodeString())
+ }
+ }
+
+ enum class Type {
+ ALLOW,
+ BLOCK;
+ }
+}
+
+enum class AllowRuleResult {
+ NO_MATCH,
+ ALLOWED,
+ BLOCKED,
+}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaBlockRemoveZone.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaBlockRemoveZone.kt
new file mode 100644
index 0000000..906d257
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaBlockRemoveZone.kt
@@ -0,0 +1,24 @@
+package nl.kallestruik.darena.types.arena
+
+import kotlinx.serialization.Serializable
+import nl.kallestruik.darena.arenas.world.ArenaWorld
+import nl.kallestruik.dlib.config.annotations.Description
+import org.bukkit.Material
+
+@Serializable
+data class ArenaBlockRemoveZone(
+ @Description(["The lower corner of the area to remove."])
+ val lower: ArenaLocation,
+ @Description(["The upper corner of the area to remove."])
+ val upper: ArenaLocation,
+) {
+ fun removeBlocks(world: ArenaWorld) {
+ for (x in lower.x.until(upper.x + 1)) {
+ for (y in lower.y.until(upper.y + 1)) {
+ for (z in lower.z.until(upper.z + 1)) {
+ world.world.getBlockAt(x, y, z).type = Material.AIR
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaBorders.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaBorders.kt
new file mode 100644
index 0000000..8568b35
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaBorders.kt
@@ -0,0 +1,15 @@
+package nl.kallestruik.darena.types.arena
+
+import kotlinx.serialization.Serializable
+import nl.kallestruik.darena.types.border.AbstractBorderNode
+import nl.kallestruik.dlib.config.annotations.Description
+
+@Serializable
+data class ArenaBorders(
+ @Description(["The vertical border of the world. This is the normal minecraft border."])
+ val vertical: List = listOf(),
+ @Description(["A horizontal border that kills everyone that is above it. This border is indicated using particle effects."])
+ val top: List = listOf(),
+ @Description(["A horizontal border that kills everyone that is below it. This border is indicated using particle effects."])
+ val bottom: List = listOf(),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaCheckpoint.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaCheckpoint.kt
index b9e733c..60fac16 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaCheckpoint.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaCheckpoint.kt
@@ -1,35 +1,30 @@
package nl.kallestruik.darena.types.arena
-import org.bukkit.configuration.ConfigurationSection
-import javax.naming.ConfigurationException
-import nl.kallestruik.darena.types.ConfigSaveable
-import nl.kallestruik.darena.types.ConfigLoadable
-import nl.kallestruik.darena.util.ConfigHelper
-import nl.kallestruik.darena.util.Logger
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.DefaultBoolean
+import nl.kallestruik.dlib.config.annotations.Description
+@Serializable
data class ArenaCheckpoint(
+ @Description(["The name of the checkpoint."])
val label: String,
+ @Description(["The lower corner of the checkpoint area."])
val lower: ArenaLocation,
+ @Description(["The upper corner of the checkpoint area."])
val upper: ArenaLocation,
- val spawn: String,
-): ConfigSaveable {
- override fun save(section: ConfigurationSection) {
- Logger.trace(ArenaCheckpoint::class, "save(section: ${section.currentPath})")
- section.set("label", label)
- lower.save(ConfigHelper.getOrCreateSection(section, "lower"))
- upper.save(ConfigHelper.getOrCreateSection(section, "upper"))
- section.set("spawn", spawn)
- }
-
- companion object: ConfigLoadable {
- override fun load(section: ConfigurationSection): ArenaCheckpoint {
- Logger.trace(ArenaCheckpoint::class, "load(section: ${section.currentPath})")
- return ArenaCheckpoint(
- section.name,
- ArenaLocation.load(section.getConfigurationSection("lower")!!),
- ArenaLocation.load(section.getConfigurationSection("upper")!!),
- section.getString("spawn") ?: throw ConfigurationException("Could not find required field spawn in '${section.currentPath}'")
- )
- }
- }
-}
+ @SerialName("spawn-rule")
+ @Description(["The spawn rule to use for this checkpoint when respawning or moving players."])
+ var spawnRule: String = "",
+ @SerialName("spawn-pool")
+ @Description(["The spawn pool to use for this checkpoint when respawning or moving players."])
+ val spawnPool: String = "",
+ @SerialName("move-player")
+ @Description(["Whether or not to teleport players to the spawn-rule/spawn-pool when they reach the checkpoint."])
+ @DefaultBoolean(false)
+ val movePlayer: Boolean = false,
+ @SerialName("is-finish")
+ @Description(["Whether or not this checkpoint counts as a finish. If it does players will be placed into spectator mode upon reaching it."])
+ @DefaultBoolean(false)
+ val isFinish: Boolean = false,
+)
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaCountdown.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaCountdown.kt
new file mode 100644
index 0000000..28a34bf
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaCountdown.kt
@@ -0,0 +1,26 @@
+package nl.kallestruik.darena.types.arena
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.DefaultInt
+import nl.kallestruik.dlib.config.annotations.Description
+import nl.kallestruik.dlib.config.annotations.MinInt
+
+@Serializable
+data class ArenaCountdown(
+ @SerialName("loading")
+ @Description(["The time to count down before teleport players into the map for spectating."])
+ @MinInt(0)
+ @DefaultInt(10)
+ var loadingTime: Int = 10,
+ @SerialName("spectator")
+ @Description(["The time players have in spectator mode before being moved to the staging area."])
+ @MinInt(0)
+ @DefaultInt(10)
+ var spectatorTime: Int = 10,
+ @SerialName("before-start")
+ @Description(["The time to count down in the staging area before removing the area defined in the block-remove-zone section."])
+ @MinInt(0)
+ @DefaultInt(10)
+ var beforeStartTime: Int = 10,
+)
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaEnchantment.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaEnchantment.kt
index cb36343..b646237 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaEnchantment.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaEnchantment.kt
@@ -1,32 +1,28 @@
package nl.kallestruik.darena.types.arena
-import org.bukkit.configuration.ConfigurationSection
-import org.bukkit.configuration.InvalidConfigurationException
-import nl.kallestruik.darena.types.ConfigListSaveable
-import nl.kallestruik.darena.types.ConfigListLoadable
+import kotlinx.serialization.Serializable
import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.dlib.config.annotations.DefaultInt
+import nl.kallestruik.dlib.config.annotations.Description
+import nl.kallestruik.dlib.config.annotations.MinInt
+import org.bukkit.NamespacedKey
+import org.bukkit.enchantments.Enchantment
+import org.bukkit.inventory.ItemStack
+@Serializable
data class ArenaEnchantment(
+ @Description(["The name of the enchantment.",
+ "", "Valid values: The id of the enchantment as shown in the /enchant command without the minecraft:",
+ "For example: sharpness, knockback, power"])
val name: String,
+ @Description(["The level of the enchantment. Allows over enchanting."])
+ @MinInt(1)
+ @DefaultInt(1)
val level: Int,
-): ConfigListSaveable {
- override fun getKey(): String {
- Logger.trace(ArenaEnchantment::class, "getKey()")
- return name
- }
+) {
+ fun addToItem(stack: ItemStack) {
+ Logger.trace(ArenaEnchantment::class, "addToItem(stack: $stack)")
+ stack.addUnsafeEnchantment(Enchantment.getByKey(NamespacedKey.minecraft(name.lowercase()))!!, level)
- override fun getValue(): Int {
- Logger.trace(ArenaEnchantment::class, "getValue()")
- return level
- }
-
- companion object: ConfigListLoadable {
- override fun loadFromList(key: String, value: Any): ArenaEnchantment {
- Logger.trace(ArenaEnchantment::class, "loadFromList(key: $key, value: $value)")
- return ArenaEnchantment(
- key,
- value as Int
- )
- }
}
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaEndConditions.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaEndConditions.kt
new file mode 100644
index 0000000..01445cf
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaEndConditions.kt
@@ -0,0 +1,25 @@
+package nl.kallestruik.darena.types.arena
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.DefaultInt
+import nl.kallestruik.dlib.config.annotations.Description
+import nl.kallestruik.dlib.config.annotations.MinInt
+
+@Serializable
+data class ArenaEndConditions(
+ @SerialName("players-left")
+ @Description(["End the game when there are this many players left alive. Set to -1 to disable."])
+ @MinInt(-1)
+ @DefaultInt(-1)
+ val playersLeft: Int = -1,
+ @Description(["End the game after this amount of seconds. Set to -1 to disable."])
+ @MinInt(-1)
+ @DefaultInt(-1)
+ val time: Int = -1,
+ @SerialName("players-finished")
+ @Description(["End the game after this amount of players reach a finish checkpoint. Set to -1 to disable."])
+ @MinInt(-1)
+ @DefaultInt(-1)
+ val playersFinished: Int = -1,
+)
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaItem.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaItem.kt
index dd8889a..3c1fe2f 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaItem.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaItem.kt
@@ -1,36 +1,70 @@
package nl.kallestruik.darena.types.arena
-import nl.kallestruik.darena.util.ConfigHelper
-import nl.kallestruik.darena.types.ConfigSaveable
-import nl.kallestruik.darena.types.ConfigLoadable
+import com.destroystokyo.paper.Namespaced
+import kotlinx.serialization.Serializable
+import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.dlib.config.annotations.*
import org.bukkit.Material
-import org.bukkit.configuration.ConfigurationSection
+import org.bukkit.NamespacedKey
+import org.bukkit.inventory.ItemStack
+@Serializable
data class ArenaItem(
+ @Description(["The item type."])
+ @DefaultString("AIR")
val material: Material = Material.AIR,
+ @Description(["The amount."])
+ @MinInt(1)
+ @DefaultInt(1)
val amount: Int = 1,
+ @Description(["The enchantments that will be applied to the item."])
val enchantments: List = listOf(),
+ @Description(["Whether the item takes durability damage or not."])
+ @DefaultBoolean(false)
val unbreakable: Boolean = false,
-): ConfigSaveable {
- override fun save(section: ConfigurationSection) {
- section.set("material", material.toString())
- section.set("amount", amount)
- ConfigHelper.saveList(ConfigHelper.getOrCreateSection(section, "enchantments"), enchantments, ArenaEnchantment)
- section.set("unbreakable", unbreakable)
- }
-
- companion object: ConfigLoadable {
- override fun load(section: ConfigurationSection): ArenaItem {
- val enchantments = section.getConfigurationSection("enchantments")?.let {
- ArenaEnchantment.loadList(it)
- } ?: listOf()
-
- return ArenaItem(
- ConfigHelper.matchMaterial(section.getString("material")!!),
- section.getInt("amount", 1),
- enchantments,
- section.getBoolean("unbreakable", false)
- )
+ @Description(["A list of blocks that this block can be placed on. Requires the feature GAMEMODE_ADVENTURE to work.",
+ "", "Valid values: all minecraft block ids as seen in game with F3+h on without the minecraft:",
+ "For example: stone, granite, andesite, diamond_block"])
+ val placeOn: Set = mutableSetOf(),
+ @Description(["A list of blocks that this item can be break. Requires the feature GAMEMODE_ADVENTURE to work.",
+ "", "Valid values: all minecraft block ids as seen in game with F3+h on without the minecraft:",
+ "For example: stone, granite, andesite, diamond_block"])
+ val canBreak: Set = mutableSetOf(),
+) {
+ fun asItemStack(): ItemStack {
+ Logger.trace(ArenaItem::class, "asItemStack()")
+ val stack = ItemStack(material, amount)
+ for (enchantment in enchantments) {
+ enchantment.addToItem(stack)
}
+
+ val meta = stack.itemMeta
+
+ if (placeOn.isNotEmpty()) {
+ val placeableKeys = mutableSetOf()
+ for (block in placeOn) {
+ //TODO: Make this give a nicer error if it fails.
+ placeableKeys.add(NamespacedKey.minecraft(block))
+ }
+
+ meta.setPlaceableKeys(placeableKeys)
+ }
+
+ if (canBreak.isNotEmpty()) {
+ val destroyableKeys = mutableSetOf()
+ for (block in canBreak) {
+ destroyableKeys.add(NamespacedKey.minecraft(block))
+ }
+
+ meta.setDestroyableKeys(destroyableKeys)
+ }
+
+ if (unbreakable) {
+ meta.isUnbreakable = true
+ }
+
+ stack.itemMeta = meta
+
+ return stack
}
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaLoadout.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaLoadout.kt
index 2c577dd..c59b7cf 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaLoadout.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaLoadout.kt
@@ -1,43 +1,48 @@
package nl.kallestruik.darena.types.arena
-import org.bukkit.configuration.ConfigurationSection
-import nl.kallestruik.darena.util.ConfigHelper
-import nl.kallestruik.darena.types.ConfigSaveable
-import nl.kallestruik.darena.types.ConfigLoadable
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.AllowedKeys
+import nl.kallestruik.dlib.config.annotations.AllowedKeysExclusive
+import nl.kallestruik.dlib.config.annotations.Description
+import org.bukkit.entity.Player
+import org.bukkit.inventory.EquipmentSlot
+@Serializable
data class ArenaLoadout(
- val name: String,
- val hotbar: List,
- val armor: List,
- val offhand: ArenaItem,
-): ConfigSaveable {
- override fun save(section: ConfigurationSection) {
- ConfigHelper.saveList(ConfigHelper.getOrCreateSection(section, "hotbar"), hotbar)
- ConfigHelper.saveList(ConfigHelper.getOrCreateSection(section, "armor"), armor)
- offhand.save(ConfigHelper.getOrCreateSection(section, "offhand"))
- }
+ @Description(["The items that will be in the players hotbar."])
+ @AllowedKeys(["0", "1", "2", "3", "4", "5", "6", "7", "8"])
+ val hotbar: MutableMap = mutableMapOf(),
+ @Description(["The items that will be in the players armor slots."])
+ @AllowedKeysExclusive(["helmet", "head"])
+ @AllowedKeysExclusive(["chestplate", "chest", "elytra"])
+ @AllowedKeysExclusive(["leggings", "legs", "pants"])
+ @AllowedKeysExclusive(["boots", "feet"])
+ val armor: MutableMap = mutableMapOf(),
+ @Description(["The item that will be in the players offhand."])
+ val offhand: ArenaItem? = null,
+ @Description(["The potion effects that will be applied to players upon spawning."])
+ val effects: MutableList = mutableListOf(),
+) {
+ fun equip(player: Player) {
+ for (entry in hotbar) {
+ val slot = entry.key.toInt()
+ player.inventory.setItem(slot, entry.value.asItemStack())
+ }
- companion object : ConfigLoadable {
- override fun load(section: ConfigurationSection): ArenaLoadout {
+ for (entry in armor) {
+ when (entry.key) {
+ "helmet", "head" -> player.inventory.setItem(EquipmentSlot.HEAD, entry.value.asItemStack())
+ "chestplate", "chest", "elytra" -> player.inventory.setItem(EquipmentSlot.CHEST, entry.value.asItemStack())
+ "leggings", "legs", "pants" -> player.inventory.setItem(EquipmentSlot.LEGS, entry.value.asItemStack())
+ "boots", "feet" -> player.inventory.setItem(EquipmentSlot.FEET, entry.value.asItemStack())
+ }
+ }
- val hotbar = section.getConfigurationSection("hotbar")?.let {
- ArenaItem.loadList(it)
- }?: listOf()
+ if (offhand != null)
+ player.inventory.setItemInOffHand(offhand.asItemStack())
- val armor = section.getConfigurationSection("armor")?.let {
- ArenaItem.loadList(it)
- }?: listOf()
-
- val offhand = section.getConfigurationSection("offhand")?.let {
- ArenaItem.load(it)
- }?: ArenaItem()
-
- return ArenaLoadout(
- section.name,
- hotbar,
- armor,
- offhand,
- )
+ for (effect in effects) {
+ effect.apply(player)
}
}
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaLocation.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaLocation.kt
index bc77685..1166534 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaLocation.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaLocation.kt
@@ -1,22 +1,24 @@
package nl.kallestruik.darena.types.arena
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.DefaultInt
+import nl.kallestruik.dlib.config.annotations.Description
import org.bukkit.configuration.ConfigurationSection
-import nl.kallestruik.darena.types.ConfigSaveable
-import nl.kallestruik.darena.types.ConfigLoadable
+@Serializable
data class ArenaLocation(
+ @Description(["The x coordinate."])
+ @DefaultInt(0)
val x: Int,
+ @Description(["The y coordinate."])
+ @DefaultInt(0)
val y: Int,
+ @Description(["The z coordinate."])
+ @DefaultInt(0)
val z: Int
-): ConfigSaveable {
- override fun save(section: ConfigurationSection) {
- section.set("x", x)
- section.set("y", y)
- section.set("z", z)
- }
-
- companion object: ConfigLoadable {
- override fun load(section: ConfigurationSection): ArenaLocation {
+) {
+ companion object {
+ fun load(section: ConfigurationSection): ArenaLocation {
return ArenaLocation(
section.getInt("x", 0),
section.getInt("y", 0),
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaPoints.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaPoints.kt
index e72d5b8..1fe521b 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaPoints.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaPoints.kt
@@ -1,31 +1,23 @@
package nl.kallestruik.darena.types.arena
-import org.bukkit.configuration.ConfigurationSection
-import nl.kallestruik.darena.util.ConfigHelper
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.Description
+@Serializable
data class ArenaPoints(
+ @Description(["The points given to a player when they get a kill."])
val kill: List = listOf(0),
+ @SerialName("last-man-standing")
+ @Description(["The points given to a player for staying alive the longest."])
val lastManStanding: List = listOf(0),
- val checkpoints: Map> = mapOf()
-) {
- fun save(section: ConfigurationSection) {
- section.set("kill", ConfigHelper.intListToString(kill))
- section.set("lastManStanding", ConfigHelper.intListToString(lastManStanding))
-
- val checkpointSection = ConfigHelper.getOrCreateSection(section, "checkpoints")
- for (entry in checkpoints) {
- checkpointSection.set(entry.key, ConfigHelper.intListToString(entry.value))
- }
-
- }
-
- companion object {
- fun load(section: ConfigurationSection): ArenaPoints {
- return ArenaPoints(
- ConfigHelper.parseIntList(section.getString("kill")!!),
- ConfigHelper.parseIntList(section.getString("lastManStanding")!!),
- ConfigHelper.parseStringIntListMap(section.getConfigurationSection("checkpoints")!!)
- )
- }
- }
-}
+ @Description(["The points given to a player for reaching a checkpoint.",
+ "","Valid keys: the label of a checkpoint defined in the checkpoints section."])
+ val checkpoints: Map> = mapOf(),
+ @Description(["The points given to a player for reaching a checkpoint that is part of a spawn pool.",
+ "The first list represents the first checkpoint, the second the second checkpoint, etc",
+ "",
+ "Valid keys: the names of spawn pools as defined in the spawn-pools section.",
+ ])
+ val pools: Map>> = mapOf(),
+)
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaPotionEffect.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaPotionEffect.kt
index ce222a5..d3d1317 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaPotionEffect.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaPotionEffect.kt
@@ -1,28 +1,30 @@
package nl.kallestruik.darena.types.arena
-import nl.kallestruik.darena.types.ConfigSaveable
-import nl.kallestruik.darena.types.ConfigLoadable
-import org.bukkit.configuration.ConfigurationSection
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.Description
+import nl.kallestruik.dlib.config.annotations.MaxInt
+import nl.kallestruik.dlib.config.annotations.MinInt
+import org.bukkit.entity.Player
+import org.bukkit.potion.PotionEffect
+import org.bukkit.potion.PotionEffectType
-class ArenaPotionEffect(
+@Serializable
+data class ArenaPotionEffect(
+ @Description(["The name of the potion effect.",
+ "", "Valid values: the name of the potion effect as seen in the /effect command without minecraft:",
+ "For example: invisibility, strength, haste"])
val name: String,
+ @Description(["The strength of the potion effect."])
+ @MinInt(0)
+ @MaxInt(255)
val strength: Int,
+ @Description(["The duration of the potion effect in ticks. (20 ticks = 1 second)"])
+ @MinInt(0)
val duration: Int,
+ @Description(["Whether the potion effect should be hidden from the player. This hides the particles and the icon."])
val hidden: Boolean
-): ConfigSaveable {
- override fun save(section: ConfigurationSection) {
- section.set("strength", strength)
- section.set("duration", duration)
- section.set("hidden", hidden)
- }
-
- companion object: ConfigLoadable {
- override fun load(section: ConfigurationSection): ArenaPotionEffect {
- return ArenaPotionEffect(
- section.name,
- section.getInt("strength"),
- section.getInt("duration"),
- section.getBoolean("hidden"))
- }
+) {
+ fun apply(player: Player) {
+ player.addPotionEffect(PotionEffect(PotionEffectType.getByName(name)!!, duration, strength, !hidden, !hidden, !hidden))
}
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawn.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawn.kt
index a4f2605..dcc4b1f 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawn.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawn.kt
@@ -1,46 +1,44 @@
package nl.kallestruik.darena.types.arena
+import kotlinx.serialization.Serializable
import nl.kallestruik.darena.arenas.world.ArenaWorld
-import nl.kallestruik.darena.util.ConfigHelper
-import nl.kallestruik.darena.types.ConfigSaveable
-import nl.kallestruik.darena.types.ConfigLoadable
+import nl.kallestruik.darena.util.Logger
+import nl.kallestruik.dlib.config.annotations.DefaultFloat
+import nl.kallestruik.dlib.config.annotations.Description
+import nl.kallestruik.dlib.config.annotations.MaxFloat
+import nl.kallestruik.dlib.config.annotations.MinFloat
import org.bukkit.Location
-import org.bukkit.configuration.ConfigurationSection
+import org.bukkit.World
import org.bukkit.entity.Player
+@Serializable
data class ArenaSpawn(
- val label: String,
+ @Description(["The x coordinate of the spawn point."])
+ @DefaultFloat(0.0)
val x: Double,
+ @Description(["The y coordinate of the spawn point."])
+ @DefaultFloat(0.0)
val y: Double,
+ @Description(["The z coordinate of the spawn point."])
+ @DefaultFloat(0.0)
val z: Double,
+ @Description(["The yaw of the spawn point."])
+ @MinFloat(-180.0)
+ @MaxFloat(180.0)
+ @DefaultFloat(0.0)
val yaw: Float,
- val pitch: Float,
- val loadout: String
-): ConfigSaveable {
+ @Description(["The pitch of the spawn point."])
+ @MinFloat(-90.0)
+ @MaxFloat(90.0)
+ @DefaultFloat(0.0)
+ val pitch: Float
+) {
fun spawn(world: ArenaWorld, player: Player) {
- player.teleport(Location(world.world, x, y, z, yaw, pitch))
+ Logger.trace(ArenaSpawn::class, "spawn(world: ${world.name}, player: ${player.name})")
+ player.teleport(toLocation(world.world))
}
- override fun save(section: ConfigurationSection) {
- section.set("x", x)
- section.set("y", y)
- section.set("z", z)
- section.set("yaw", yaw.toDouble())
- section.set("pitch", pitch.toDouble())
- section.set("loadout", loadout)
- }
-
- companion object: ConfigLoadable {
- override fun load(section: ConfigurationSection): ArenaSpawn {
- return ArenaSpawn(
- section.name,
- section.getDouble("x"),
- section.getDouble("y"),
- section.getDouble("z"),
- section.getDouble("yaw").toFloat(),
- section.getDouble("pitch").toFloat(),
- section.getString("loadout")!!
- )
- }
+ fun toLocation(world: World): Location {
+ return Location(world, x, y, z, yaw, pitch)
}
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawnPool.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawnPool.kt
new file mode 100644
index 0000000..02befdd
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawnPool.kt
@@ -0,0 +1,19 @@
+package nl.kallestruik.darena.types.arena
+
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.DefaultInt
+import nl.kallestruik.dlib.config.annotations.Description
+import nl.kallestruik.dlib.config.annotations.MinInt
+
+@Serializable
+data class ArenaSpawnPool(
+ @Description(["The name of the spawn pool."])
+ val label: String,
+ @Description(["The amount of spawn rules that will be chosen before a player is considered finished."])
+ @MinInt(1)
+ @DefaultInt(-1)
+ val times: Int,
+ @Description(["The list of spawn rules that the pool can choose from.",
+ "", "Valid values: any spawn rule defined in the spawn-rules section."])
+ val rules: List
+)
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawnRule.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawnRule.kt
index 52bc12f..61d3c26 100644
--- a/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawnRule.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ArenaSpawnRule.kt
@@ -1,27 +1,27 @@
package nl.kallestruik.darena.types.arena
-import nl.kallestruik.darena.types.ConfigLoadable
-import nl.kallestruik.darena.types.ConfigSaveable
-import nl.kallestruik.darena.util.ConfigHelper
-import org.bukkit.configuration.ConfigurationSection
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import nl.kallestruik.dlib.config.annotations.DefaultBoolean
+import nl.kallestruik.dlib.config.annotations.DefaultString
+import nl.kallestruik.dlib.config.annotations.Description
+@Serializable
class ArenaSpawnRule(
- val reuseSpawns: Boolean,
- val effects: Map,
- val spawns: List
-): ConfigSaveable {
- override fun save(section: ConfigurationSection) {
- section.set("reuseSpawns", reuseSpawns)
- ConfigHelper.saveMap(ConfigHelper.getOrCreateSection(section, "effects"), effects)
- section.set("spawns", spawns)
- }
-
- companion object: ConfigLoadable {
- override fun load(section: ConfigurationSection): ArenaSpawnRule {
- return ArenaSpawnRule(
- section.getBoolean("reuseSpawn", true),
- ConfigHelper.loadMap(section.getConfigurationSection("effects")!!, ArenaPotionEffect),
- section.getStringList("spawns"))
- }
- }
-}
+ @SerialName("reuse-spawns")
+ @Description(["Whether or not to reuse the spawns for different players."])
+ @DefaultBoolean(true)
+ val reuseSpawns: Boolean = true,
+ @SerialName("reuse-individual")
+ @Description(["Whether or not to reuse the spawns for individual players.",
+ "When this is set a player will always use the same spawn after spawning there once."])
+ @DefaultBoolean(false)
+ val reuseIndividual: Boolean = false,
+ @Description(["The list of spawns that this spawn rule can pick from.",
+ "", "Valid values: any spawn name defined in the spawns section."])
+ val spawns: MutableList = mutableListOf(),
+ @Description(["The loadout to use for players that are spawned using this spawn rule. Use none to not give them any items.",
+ "", "Valid values: any loadout defined in the loadouts section or none."])
+ @DefaultString("none")
+ val loadout: String = "none"
+)
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaCheckpoint.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaCheckpoint.kt
new file mode 100644
index 0000000..93ecb1d
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaCheckpoint.kt
@@ -0,0 +1,29 @@
+package nl.kallestruik.darena.types.arena
+
+import org.bukkit.Location
+
+data class ProcessedArenaCheckpoint(
+ val label: String,
+ val lower: ArenaLocation,
+ val upper: ArenaLocation,
+ var spawnRule: ProcessedArenaSpawnRule?,
+ val spawnPool: ProcessedArenaSpawnPool?,
+ val movePlayer: Boolean,
+ val isFinish: Boolean,
+) {
+ constructor(checkpoint: ArenaCheckpoint, spawnRule: ProcessedArenaSpawnRule?, spawnPool: ProcessedArenaSpawnPool?)
+ : this(checkpoint.label, checkpoint.lower, checkpoint.upper, spawnRule, spawnPool, checkpoint.movePlayer, checkpoint.isFinish)
+
+ fun isInside(location: Location): Boolean {
+ return location.blockX <= upper.x && location.blockX >= lower.x
+ && location.blockY <= upper.y && location.blockY >= lower.y
+ && location.blockZ <= upper.z && location.blockZ >= lower.z
+ }
+
+ fun resolveSpawnRule() {
+ if (spawnRule != null)
+ return
+
+ spawnRule = spawnPool!!.nextRule()
+ }
+}
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaSpawnPool.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaSpawnPool.kt
new file mode 100644
index 0000000..d02226b
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaSpawnPool.kt
@@ -0,0 +1,11 @@
+package nl.kallestruik.darena.types.arena
+
+data class ProcessedArenaSpawnPool(
+ val label: String,
+ val times: Int,
+ val rules: MutableList
+) {
+ fun nextRule(): ProcessedArenaSpawnRule? {
+ return rules.removeFirstOrNull()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaSpawnRule.kt b/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaSpawnRule.kt
new file mode 100644
index 0000000..a0bcac4
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/arena/ProcessedArenaSpawnRule.kt
@@ -0,0 +1,55 @@
+package nl.kallestruik.darena.types.arena
+
+import nl.kallestruik.darena.arenas.world.ArenaWorld
+import nl.kallestruik.darena.util.ArenaUtil
+import nl.kallestruik.darena.util.Logger
+import org.bukkit.entity.Player
+import java.util.*
+
+data class ProcessedArenaSpawnRule(
+ val reuseSpawns: Boolean,
+ val reuseIndividual: Boolean,
+ val spawns: List,
+ val loadout: ArenaLoadout?,
+) {
+ var lastSpawnIndex = -1
+ private val lastForPlayer = mutableMapOf()
+
+ constructor(spawnRule: ArenaSpawnRule, spawns: List, loadout: ArenaLoadout?): this(spawnRule.reuseSpawns, spawnRule.reuseIndividual, spawns, loadout)
+
+ fun spawnPlayers(players: Collection, world: ArenaWorld) {
+ Logger.trace(ProcessedArenaSpawnRule::class, "spawnPlayers(players: $players, world: ${world.name})")
+ players.forEach() {player ->
+ spawnPlayer(player, world)
+ }
+ }
+
+ fun spawnPlayer(player: Player, world: ArenaWorld) {
+ if (player.health <= 0) {
+ player.spigot().respawn()
+ }
+
+ Logger.trace(ProcessedArenaSpawnRule::class, "spawnPlayer(player: ${player.name}, world: ${world.name})")
+ if (player.uniqueId in lastForPlayer) {
+ spawnAndEquip(player, world, spawns[lastForPlayer[player.uniqueId]!!])
+ return
+ }
+
+ if (!reuseSpawns && lastSpawnIndex + 1 >= spawns.size) {
+ Logger.warn(ProcessedArenaSpawnRule::class, "Cant spawn player \"${player.name}\" because there are no spots left and reuse-spawns is set to false.")
+ return
+ }
+
+ val nextSpawnIndex = (lastSpawnIndex + 1) % spawns.size
+ spawnAndEquip(player, world, spawns[nextSpawnIndex])
+ lastForPlayer[player.uniqueId] = nextSpawnIndex
+
+ lastSpawnIndex++
+ }
+
+ private fun spawnAndEquip(player: Player, world: ArenaWorld, spawn: ArenaSpawn) {
+ ArenaUtil.clearPlayer(player)
+ spawn.spawn(world, player)
+ loadout?.equip(player)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/border/AbstractBorder.kt b/src/main/kotlin/nl/kallestruik/darena/types/border/AbstractBorder.kt
new file mode 100644
index 0000000..dc9d7c0
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/border/AbstractBorder.kt
@@ -0,0 +1,8 @@
+package nl.kallestruik.darena.types.border
+
+abstract class AbstractBorder {
+
+ abstract fun size(size: Double, time: Long = 0)
+
+ abstract fun center(x: Double, y: Double)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/border/AbstractBorderNode.kt b/src/main/kotlin/nl/kallestruik/darena/types/border/AbstractBorderNode.kt
new file mode 100644
index 0000000..2e663d9
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/border/AbstractBorderNode.kt
@@ -0,0 +1,53 @@
+package nl.kallestruik.darena.types.border
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.Transient
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import org.bukkit.plugin.java.JavaPlugin
+
+@Serializable(AbstractBorderNode.BorderScriptSerializer::class)
+abstract class AbstractBorderNode {
+ @Transient
+ var next: AbstractBorderNode? = null
+
+ abstract fun execute(border: AbstractBorder, plugin: JavaPlugin)
+
+ abstract fun toCommand(): String
+
+ protected fun runNext(border: AbstractBorder, plugin: JavaPlugin) {
+ next?.execute(border, plugin)
+ }
+
+ open fun cancel() {
+ next?.cancel()
+ }
+
+ object BorderScriptSerializer: KSerializer {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BorderScriptNode", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: AbstractBorderNode) {
+ encoder.encodeString(value.toCommand())
+ }
+
+ override fun deserialize(decoder: Decoder): AbstractBorderNode {
+ return createNode(decoder.decodeString())
+ }
+
+ private fun createNode(command: String): AbstractBorderNode {
+ val parts = command.split(" ", limit = 2)
+ return when (parts[0].uppercase()) {
+ "CENTER" -> BorderNodeCenter.fromParameters(parts[1])
+ "SET" -> BorderNodeSet.fromParameters(parts[1])
+ "TRANSITION" -> BorderNodeTransition.fromParameters(parts[1])
+ "WAIT" -> BorderNodeWait.fromParameters(parts[1])
+ else -> throw SerializationException("${parts[0]} is not a valid border script keyword.")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeCenter.kt b/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeCenter.kt
new file mode 100644
index 0000000..b5bd877
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeCenter.kt
@@ -0,0 +1,25 @@
+package nl.kallestruik.darena.types.border
+
+import org.bukkit.plugin.java.JavaPlugin
+
+class BorderNodeCenter(
+ private val x: Double,
+ private val y: Double,
+): AbstractBorderNode() {
+
+ override fun execute(border: AbstractBorder, plugin: JavaPlugin) {
+ border.center(x, y)
+ runNext(border, plugin)
+ }
+
+ override fun toCommand(): String {
+ return "CENTER $x $y"
+ }
+
+ companion object {
+ fun fromParameters(params: String): BorderNodeCenter {
+ val parts = params.split(" ")
+ return BorderNodeCenter(parts[0].toDouble(), parts[1].toDouble())
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeSet.kt b/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeSet.kt
new file mode 100644
index 0000000..511f213
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeSet.kt
@@ -0,0 +1,23 @@
+package nl.kallestruik.darena.types.border
+
+import org.bukkit.plugin.java.JavaPlugin
+
+class BorderNodeSet(
+ private val size: Double
+): AbstractBorderNode() {
+
+ override fun execute(border: AbstractBorder, plugin: JavaPlugin) {
+ border.size(size)
+ runNext(border, plugin)
+ }
+
+ override fun toCommand(): String {
+ return "SET $size"
+ }
+
+ companion object {
+ fun fromParameters(params: String): BorderNodeSet {
+ return BorderNodeSet(params.toDouble())
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeTransition.kt b/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeTransition.kt
new file mode 100644
index 0000000..131d0fe
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeTransition.kt
@@ -0,0 +1,25 @@
+package nl.kallestruik.darena.types.border
+
+import org.bukkit.plugin.java.JavaPlugin
+
+class BorderNodeTransition(
+ private val size: Double,
+ private val time: Long
+): AbstractBorderNode() {
+
+ override fun execute(border: AbstractBorder, plugin: JavaPlugin) {
+ border.size(size, time)
+ runNext(border, plugin)
+ }
+
+ override fun toCommand(): String {
+ return "TRANSITION $size $time"
+ }
+
+ companion object {
+ fun fromParameters(params: String): BorderNodeTransition {
+ val parts = params.split(" ")
+ return BorderNodeTransition(parts[0].toDouble(), parts[1].toLong())
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeWait.kt b/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeWait.kt
new file mode 100644
index 0000000..2df5e9f
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/border/BorderNodeWait.kt
@@ -0,0 +1,33 @@
+package nl.kallestruik.darena.types.border
+
+import org.bukkit.plugin.java.JavaPlugin
+import org.bukkit.scheduler.BukkitRunnable
+
+class BorderNodeWait(
+ private val time: Long
+): AbstractBorderNode() {
+ private var runnable: BukkitRunnable? = null
+
+ override fun execute(border: AbstractBorder, plugin: JavaPlugin) {
+ object : BukkitRunnable() {
+ override fun run() {
+ runNext(border, plugin)
+ }
+ }.runTaskLater(plugin, time * 20)
+ }
+
+ override fun toCommand(): String {
+ return "WAIT $time"
+ }
+
+ override fun cancel() {
+ super.cancel()
+ runnable?.cancel()
+ }
+
+ companion object {
+ fun fromParameters(params: String): BorderNodeWait {
+ return BorderNodeWait(params.toLong())
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/border/HorizontalBorder.kt b/src/main/kotlin/nl/kallestruik/darena/types/border/HorizontalBorder.kt
new file mode 100644
index 0000000..a83c5bd
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/border/HorizontalBorder.kt
@@ -0,0 +1,16 @@
+package nl.kallestruik.darena.types.border
+
+class HorizontalBorder: AbstractBorder() {
+ var height = 0
+ //TODO: Implement visible border in the form of a block being placed.
+ //TODO: Implement visible border in the form of particles if a player is close.
+
+ override fun size(size: Double, time: Long) {
+ height = size.toInt()
+ //TODO: Implement transition over time.
+ }
+
+ override fun center(x: Double, y: Double) {
+ //TODO: Maybe throw exception or something
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/border/MCBorder.kt b/src/main/kotlin/nl/kallestruik/darena/types/border/MCBorder.kt
new file mode 100644
index 0000000..52b9058
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/border/MCBorder.kt
@@ -0,0 +1,15 @@
+package nl.kallestruik.darena.types.border
+
+import nl.kallestruik.darena.arenas.world.ArenaWorld
+
+class MCBorder(
+ private val world: ArenaWorld
+): AbstractBorder() {
+ override fun size(size: Double, time: Long) {
+ world.world.worldBorder.setSize(size, time)
+ }
+
+ override fun center(x: Double, y: Double) {
+ world.world.worldBorder.setCenter(x, y)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/countdown/AsyncCountdown.kt b/src/main/kotlin/nl/kallestruik/darena/types/countdown/AsyncCountdown.kt
new file mode 100644
index 0000000..e85f3ef
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/countdown/AsyncCountdown.kt
@@ -0,0 +1,50 @@
+package nl.kallestruik.darena.types.countdown
+
+import nl.kallestruik.darena.util.CountdownHelper
+import org.bukkit.Bukkit
+import org.bukkit.entity.Player
+import org.bukkit.plugin.java.JavaPlugin
+import org.bukkit.scheduler.BukkitRunnable
+import org.bukkit.scheduler.BukkitTask
+
+
+open class AsyncCountdown(
+ val plugin: JavaPlugin,
+ val time: Int,
+ val subtitle: String,
+ val alwaysAsTitle: Boolean = false,
+ val alwaysAsActionBar: Boolean = false,
+ val after: Runnable,
+ ): Countdown {
+ var task: BukkitTask? = null
+
+ override fun start(players: Collection): Countdown {
+ var currentTime = time
+ task = object : BukkitRunnable() {
+ override fun run() {
+ if (currentTime > 0) {
+ if (!alwaysAsActionBar && (alwaysAsTitle || currentTime <= 5 || currentTime % 10 == 0)) {
+ CountdownHelper.sendTitleToAll(players, currentTime, subtitle)
+ CountdownHelper.sendActionBarToAll(players, currentTime, subtitle)
+ } else {
+ CountdownHelper.sendActionBarToAll(players, currentTime, subtitle)
+ }
+
+ currentTime--
+ } else {
+ CountdownHelper.clearTitleForAll(players)
+ CountdownHelper.clearActionBarForAll(players)
+ Bukkit.getScheduler().runTask(plugin, after)
+ this@AsyncCountdown.cancel()
+ }
+ }
+ }.runTaskTimerAsynchronously(plugin, 0, 20)
+
+ return this
+ }
+
+ override fun cancel() {
+ task?.cancel()
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/countdown/AsyncCountdownWithSyncTask.kt b/src/main/kotlin/nl/kallestruik/darena/types/countdown/AsyncCountdownWithSyncTask.kt
new file mode 100644
index 0000000..d2bbf9d
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/countdown/AsyncCountdownWithSyncTask.kt
@@ -0,0 +1,22 @@
+package nl.kallestruik.darena.types.countdown
+
+import org.bukkit.Bukkit
+import org.bukkit.entity.Player
+import org.bukkit.plugin.java.JavaPlugin
+
+class AsyncCountdownWithSyncTask(
+ plugin: JavaPlugin,
+ time: Int,
+ subtitle: String,
+ private val during: Runnable,
+ after: Runnable,
+ alwaysAsTitle: Boolean = false,
+ alwaysAsActionBar: Boolean = false,
+): AsyncCountdown(plugin, time, subtitle, alwaysAsTitle, alwaysAsActionBar, after) {
+ override fun start(players: Collection): Countdown {
+ super.start(players)
+ Bukkit.getScheduler().runTask(plugin, during)
+
+ return this
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/types/countdown/Countdown.kt b/src/main/kotlin/nl/kallestruik/darena/types/countdown/Countdown.kt
new file mode 100644
index 0000000..b31eeb7
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/types/countdown/Countdown.kt
@@ -0,0 +1,9 @@
+package nl.kallestruik.darena.types.countdown
+
+import org.bukkit.entity.Player
+
+interface Countdown {
+ fun start(players: Collection): Countdown
+
+ fun cancel()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/util/ArenaUtil.kt b/src/main/kotlin/nl/kallestruik/darena/util/ArenaUtil.kt
index c8ce0cd..687e1f7 100644
--- a/src/main/kotlin/nl/kallestruik/darena/util/ArenaUtil.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/util/ArenaUtil.kt
@@ -1,10 +1,14 @@
package nl.kallestruik.darena.util
+import nl.kallestruik.darena.arenas.Arena
+import nl.kallestruik.darena.managers.ArenaManager
import org.bukkit.Bukkit
import org.bukkit.GameMode
+import org.bukkit.entity.Entity
import org.bukkit.entity.Player
+import org.bukkit.entity.Projectile
-class ArenaUtil {
+object ArenaUtil {
fun getPossibleParticipants(): List {
Logger.trace(ArenaUtil::class, "getPossibleParticipants()")
@@ -15,4 +19,60 @@ class ArenaUtil {
Logger.trace(ArenaUtil::class, "getPossibleSpectators()")
return Bukkit.getOnlinePlayers().filter { it.gameMode == GameMode.SPECTATOR }
}
+
+ fun clearPlayer(player: Player) {
+ player.inventory.close()
+ player.inventory.clear()
+
+ player.exp = 0F
+ player.level = 0
+ player.health = 20.0
+ player.saturation = 0F
+ player.foodLevel = 20
+ player.fireTicks = 0
+ player.fallDistance = 0F
+ for (effect in player.activePotionEffects) {
+ player.removePotionEffect(effect.type)
+ }
+ }
+
+ fun resolveDamager(damager: Entity): Player? {
+ if (damager is Player)
+ return damager
+
+ if (damager is Projectile && damager.shooter is Player)
+ return damager.shooter as Player
+
+ return null
+ }
+
+ fun formatTime(timeLeft: Int): String {
+ val minutes = (timeLeft / 60).toString().padStart(2, '0')
+ val seconds = (timeLeft % 60).toString().padStart(2, '0')
+ return "$minutes:$seconds"
+ }
+
+ fun formatTimeMillis(timeLeft: Long): String {
+ val minutes = (timeLeft / 1000 / 60).toString().padStart(2, '0')
+ val seconds = ((timeLeft / 1000) % 60).toString().padStart(2, '0')
+ val millis = (timeLeft % 1000).toString().padStart(3, '0')
+ return "$minutes:$seconds.$millis"
+ }
+
+ fun finishPlayer(player: Player, arena: Arena, arenaManager: ArenaManager) {
+ Logger.trace(ArenaManager::class, "finishPlayer(player: $player, arena: ${arena.config.name}, arenaManager: $arenaManager)")
+ arena.makeSpectator(player)
+ arena.session!!.playersFinished++
+ if (arena.config.endConditions.playersFinished != -1
+ && arena.session!!.playersFinished >= arena.config.endConditions.playersFinished
+ ) {
+ arenaManager.end()
+ return
+ }
+
+ if (arenaManager.currentArena!!.session!!.participants.size <= arenaManager.currentArena!!.config.endConditions.playersLeft) {
+ arenaManager.end()
+ return
+ }
+ }
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/util/ConfigHelper.kt b/src/main/kotlin/nl/kallestruik/darena/util/ConfigHelper.kt
index e377c59..25708b4 100644
--- a/src/main/kotlin/nl/kallestruik/darena/util/ConfigHelper.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/util/ConfigHelper.kt
@@ -1,14 +1,12 @@
package nl.kallestruik.darena.util
import nl.kallestruik.darena.exceptions.MaterialNotFoundException
-import nl.kallestruik.darena.types.ConfigSaveable
-import nl.kallestruik.darena.types.ConfigLoadable
-import nl.kallestruik.darena.types.ConfigListSaveable
-import nl.kallestruik.darena.types.ConfigListLoadable
import org.bukkit.Material
-import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.configuration.ConfigurationSection
-import java.io.*
+import org.bukkit.configuration.file.YamlConfiguration
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
object ConfigHelper {
@@ -54,40 +52,6 @@ object ConfigHelper {
return section.createSection(key)
}
- fun saveMap(section: ConfigurationSection, map: Map) {
- Logger.trace(ConfigHelper::class, "saveMap(section: ${section.currentPath}, map: ${map.toString()})")
- for (entry in map.entries) {
- entry.value.save(ConfigHelper.getOrCreateSection(section, entry.key))
- }
- }
-
- fun saveList(section: ConfigurationSection, list: List) {
- Logger.trace(ConfigHelper::class, "saveList(section: ${section.currentPath}, list: ${list.toString()})")
- for (item in list) {
- section.set(item.getKey(), item.getValue())
- }
- }
-
- fun > loadMap(section: ConfigurationSection, loader: T): MutableMap {
- Logger.trace(ConfigHelper::class, "loadMap(section: ${section.currentPath}, loader: ${loader::class.qualifiedName})")
- val map = mutableMapOf()
- for (key in section.getKeys(false)) {
- map.put(key, loader.load(section.getConfigurationSection(key)!!))
- }
-
- return map
- }
-
- fun > loadList(section: ConfigurationSection, loader: T): MutableList {
- Logger.trace(ConfigHelper::class, "loadList(section: ${section.currentPath}, loader: ${loader::class.qualifiedName})")
- val list = mutableListOf()
- for (key in section.getKeys(false)) {
- loader.loadFromList(key, section.get(key)!!)
- }
-
- return list
- }
-
@Throws(MaterialNotFoundException::class)
fun matchMaterial(material: String): Material {
Logger.trace(ConfigHelper::class, "matchMaterial(material: $material)")
@@ -97,6 +61,7 @@ object ConfigHelper {
fun parseIntList(string: String): List {
Logger.trace(ConfigHelper::class, "parseIntList(string: $string)")
val list = mutableListOf()
+
for (key in string.split(" ")) {
list.add(key.toInt())
}
@@ -113,14 +78,4 @@ object ConfigHelper {
return sb.toString().trim()
}
-
- fun parseStringIntListMap(section: ConfigurationSection): Map> {
- Logger.trace(ConfigHelper::class, "parseStringIntListMap(section: ${section.currentPath})")
- val map = mutableMapOf>()
- for (key in section.getKeys(true)) {
- map[key] = parseIntList(section.getString(key)!!)
- }
-
- return map
- }
}
diff --git a/src/main/kotlin/nl/kallestruik/darena/util/CountdownHelper.kt b/src/main/kotlin/nl/kallestruik/darena/util/CountdownHelper.kt
new file mode 100644
index 0000000..9a6f254
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/util/CountdownHelper.kt
@@ -0,0 +1,50 @@
+package nl.kallestruik.darena.util
+
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.TextColor
+import net.kyori.adventure.title.Title
+import org.bukkit.Sound
+import org.bukkit.entity.Player
+import java.time.Duration
+
+
+object CountdownHelper {
+
+ fun sendTitleToAll(players: Collection, timeLeft: Int, subtitle: String) {
+ for (player in players) {
+ player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_BIT, 1.0F, 0.5F)
+ player.showTitle(
+ Title.title(
+ Component.text(timeLeft)
+ .color(TextColor.color(0xFFAA00)),
+ Component.text("$subtitle..."),
+ Title.Times.of(Duration.ZERO, Duration.ofSeconds(3), Duration.ofSeconds(2))
+ ))
+ }
+ }
+
+ fun sendActionBarToAll(players: Collection, timeLeft: Int, prefix: String) {
+ for (player in players) {
+ player.sendActionBar(
+ Component.text("$prefix: ")
+ .append(
+ Component
+ .text(timeLeft)
+ .color(TextColor.color(0xFFAA00))
+ )
+ )
+ }
+ }
+
+ fun clearTitleForAll(players: Collection) {
+ for (player in players) {
+ player.clearTitle()
+ }
+ }
+
+ fun clearActionBarForAll(players: Collection) {
+ for (player in players) {
+ player.sendActionBar(Component.empty())
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/nl/kallestruik/darena/util/Logger.kt b/src/main/kotlin/nl/kallestruik/darena/util/Logger.kt
index 0c9a38d..a6f2aa6 100644
--- a/src/main/kotlin/nl/kallestruik/darena/util/Logger.kt
+++ b/src/main/kotlin/nl/kallestruik/darena/util/Logger.kt
@@ -3,8 +3,7 @@ package nl.kallestruik.darena.util
import kotlin.reflect.KClass
object Logger {
- var level: LogLevel = LogLevel.INFO
-
+ var level: LogLevel = LogLevel.TRACE
fun trace(source: KClass<*>, message: String) {
if (level.shouldLog(LogLevel.TRACE))
@@ -36,7 +35,7 @@ object Logger {
println("[CRITICAL] [${source.qualifiedName}] $message")
}
- enum LogLevel(
+ enum class LogLevel(
val weight: Int
) {
TRACE(100),
@@ -44,7 +43,7 @@ object Logger {
INFO(60),
WARN(40),
ERROR(20),
- CRITICAL(0)
+ CRITICAL(0);
fun shouldLog(level: LogLevel): Boolean {
return level.weight <= weight
diff --git a/src/main/kotlin/nl/kallestruik/darena/util/RenderUtil.kt b/src/main/kotlin/nl/kallestruik/darena/util/RenderUtil.kt
new file mode 100644
index 0000000..f827d2d
--- /dev/null
+++ b/src/main/kotlin/nl/kallestruik/darena/util/RenderUtil.kt
@@ -0,0 +1,228 @@
+package nl.kallestruik.darena.util
+
+import org.bukkit.Color
+import org.bukkit.Particle
+import org.bukkit.World
+
+object RenderUtil {
+ private const val AABB_PARTICLES_PER_BLOCK = 2
+ private const val PLANE_PARTICLES_PER_BLOCK = 1
+
+ fun drawXZPlane(world: World,
+ lowerX: Int, lowerZ: Int,
+ upperX: Int, upperZ: Int,
+ y: Int, color: Color) {
+
+ for (x in upperX downTo lowerX) {
+ for (z in upperZ downTo lowerZ) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ x + 0.5,
+ y + 0.5,
+ z + 0.5,
+ PLANE_PARTICLES_PER_BLOCK,
+ 0.5,
+ 0.0,
+ 0.5,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+ }
+ }
+
+ fun drawAABB(world: World,
+ lowerX: Int, lowerY: Int, lowerZ: Int,
+ upperX: Int, upperY: Int, upperZ: Int,
+ color: Color) {
+ // h--g
+ // /| /|
+ // e--f | y z
+ // | d|-c | /
+ // |/ |/ | /
+ // a--b 0 ---> x
+ //
+ // a = lower
+ // g = upper
+
+
+ // AB
+ for (x in upperX downTo lowerX) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ x + 0.5,
+ lowerY.toDouble(),
+ lowerZ.toDouble(),
+ AABB_PARTICLES_PER_BLOCK,
+ 0.5,
+ 0.0,
+ 0.0,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // AD
+ for (z in upperZ downTo lowerZ) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ lowerX.toDouble(),
+ lowerY.toDouble(),
+ z + 0.5,
+ AABB_PARTICLES_PER_BLOCK,
+ 0.0,
+ 0.0,
+ 0.5,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // AE
+ for (y in upperY downTo lowerY) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ lowerX.toDouble(),
+ y + 0.5,
+ lowerZ.toDouble(),
+ AABB_PARTICLES_PER_BLOCK,
+ 0.0,
+ 0.5,
+ 0.0,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // GH
+ for (x in upperX downTo lowerX) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ x + 0.5,
+ upperY + 1.0,
+ upperZ + 1.0,
+ AABB_PARTICLES_PER_BLOCK,
+ 0.5,
+ 0.0,
+ 0.0,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // FG
+ for (z in upperZ downTo lowerZ) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ upperX + 1.0,
+ upperY + 1.0,
+ z + 0.5,
+ AABB_PARTICLES_PER_BLOCK,
+ 0.0,
+ 0.0,
+ 0.5,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // CG
+ for (y in upperY downTo lowerY) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ upperX + 1.0,
+ y + 0.5,
+ upperZ + 1.0,
+ AABB_PARTICLES_PER_BLOCK,
+ 0.0,
+ 0.5,
+ 0.0,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // BF
+ for (y in upperY downTo lowerY) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ upperX + 1.0,
+ y + 0.5,
+ lowerZ.toDouble(),
+ AABB_PARTICLES_PER_BLOCK,
+ 0.0,
+ 0.5,
+ 0.0,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // DH
+ for (y in upperY downTo lowerY) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ lowerX.toDouble(),
+ y + 0.5,
+ upperZ + 1.0,
+ AABB_PARTICLES_PER_BLOCK,
+ 0.0,
+ 0.5,
+ 0.0,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // AF
+ for (x in upperX downTo lowerX) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ x + 0.5,
+ upperY + 1.0,
+ lowerZ.toDouble(),
+ AABB_PARTICLES_PER_BLOCK,
+ 0.5,
+ 0.0,
+ 0.0,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // AH
+ for (z in upperZ downTo lowerZ) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ lowerX.toDouble(),
+ upperY + 1.0,
+ z + 0.5,
+ AABB_PARTICLES_PER_BLOCK,
+ 0.0,
+ 0.0,
+ 0.5,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // BC
+ for (z in upperZ downTo lowerZ) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ upperX + 1.0,
+ lowerY.toDouble(),
+ z + 0.5,
+ AABB_PARTICLES_PER_BLOCK,
+ 0.0,
+ 0.0,
+ 0.5,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+
+ // CD
+ for (x in upperX downTo lowerX) {
+ world.spawnParticle(
+ Particle.REDSTONE,
+ x + 0.5,
+ lowerY.toDouble(),
+ upperZ + 1.0,
+ AABB_PARTICLES_PER_BLOCK,
+ 0.5,
+ 0.0,
+ 0.0,
+ Particle.DustOptions(color, 1.0F)
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 61b8cb5..0d22cb4 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -4,4 +4,57 @@ main: nl.kallestruik.darena.DArena
api-version: 1.16
depend:
- "DLib"
-commands: []
\ No newline at end of file
+commands:
+ darena:
+ leaderboard:
+permissions:
+ darena.debug:
+ default: op
+ darena.start:
+ default: op
+ darena.end:
+ default: op
+ darena.list:
+ default: op
+ darena.leaderboard:
+ default: true
+ darena.reload:
+ default: op
+ darena.new:
+ default: op
+ darena.edit.load:
+ default: op
+ darena.edit.save:
+ default: op
+ darena.edit.tp:
+ default: op
+ darena.edit.finish:
+ default: op
+ darena.team.create:
+ default: op
+ darena.team.join:
+ default: op
+ darena.team.leave:
+ default: op
+ darena.team.remove:
+ default: op
+ darena.team.remove-all:
+ default: op
+ darena.team.auto:
+ default: op
+ darena.team.option:
+ default: op
+ darena.team.list:
+ default: op
+ darena.points.add:
+ default: op
+ darena.points.remove:
+ default: op
+ darena.points.reset:
+ default: op
+ darena.edit.toggle.spawnplacing:
+ default: op
+ darena.edit.visualize.spawns:
+ default: op
+ darena.edit.visualize.checkpoints:
+ default: op
\ No newline at end of file
diff --git a/src/main/resources/template/arena.yml b/src/main/resources/template/arena.yml
index 70fa6a9..e36277e 100644
--- a/src/main/resources/template/arena.yml
+++ b/src/main/resources/template/arena.yml
@@ -35,14 +35,14 @@
#loadouts:
# default:
# hotbar:
-# weapon:
+# 0:
# material: iron_axe
# amount: 1
# enchantments:
# name: sharpness
# level: 5
# unbreakable: true
-# food:
+# 1:
# material: cooked_beef
# amount: 10
# armor:
@@ -72,6 +72,7 @@
# y: 10
# z: 10
# spawnRule: "label1"
+# move-player: false
# checkpoint2:
# lower:
# x: 20
@@ -82,6 +83,7 @@
# y: 10
# z: 30
# spawnRule: "label2"
+# move-player: false
# finish:
# lower:
# x: 40
@@ -92,6 +94,7 @@
# y: 10
# z: 50
# spawnRule: "spectator"
+# move-player: true
#points:
# kill: 10 5
# last-man-standing: 10 5 3 0
@@ -99,6 +102,11 @@
# checkpoint1: 10
# checkpoint2: 5
# finish: 20 10 5
+# pools:
+# maps:
+# - 10 5
+# - 12 6
+# - 14 7
#spawnRules:
# initial:
# reuse-spawns: true
@@ -107,7 +115,7 @@
# strength: 1
# duration: 10
# hidden: false
-# invisability:
+# invisibility:
# strength: 1
# duration: 100000000
# hidden: true
@@ -118,3 +126,62 @@
# reuse-spawns: true
# spawns:
# - "spectatorSpawn"
+# map1:
+# reuse-spawns: false
+# reuse-individual: true
+# spawns:
+# - "exampleSpawn1"
+# - "exampleSpawn2"
+# - "exampleSpawn3"
+# map2:
+# reuse-spawns: false
+# reuse-individual: true
+# spawns:
+# - "exampleSpawn1"
+# - "exampleSpawn2"
+# - "exampleSpawn3"
+# map3:
+# reuse-spawns: false
+# reuse-individual: true
+# spawns:
+# - "exampleSpawn1"
+# - "exampleSpawn2"
+# - "exampleSpawn3"
+# map4:
+# reuse-spawns: false
+# reuse-individual: true
+# spawns:
+# - "exampleSpawn1"
+# - "exampleSpawn2"
+# - "exampleSpawn3"
+# map5:
+# reuse-spawns: false
+# reuse-individual: true
+# spawns:
+# - "exampleSpawn1"
+# - "exampleSpawn2"
+# - "exampleSpawn3"
+#spawn-pools:
+# maps:
+# times: 3
+# rules:
+# - map1
+# - map2
+# - map3
+# - map4
+# - map5
+#borders:
+# vertical:
+# - CENTER 0 0
+# - SET 100.0
+# - WAIT 400
+# - TRANSITION 80.5 200
+# top:
+# - SET 256.0
+# bottom:
+# - SET 80.0
+#end-conditions:
+# players-left: 1
+# time: 60
+# players-finished: 5
+
diff --git a/src/main/resources/template/config.yml b/src/main/resources/template/config.yml
index e69de29..d77f101 100644
--- a/src/main/resources/template/config.yml
+++ b/src/main/resources/template/config.yml
@@ -0,0 +1,8 @@
+spawn-pos:
+ x: 0
+ y: 100
+ z: 0
+spawn-world: world
+
+cooldown-fireball: 30
+
diff --git a/src/main/resources/template/games.yml b/src/main/resources/template/games.yml
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/template/teams.yml b/src/main/resources/template/teams.yml
index e69de29..1b50592 100644
--- a/src/main/resources/template/teams.yml
+++ b/src/main/resources/template/teams.yml
@@ -0,0 +1 @@
+auto-teams: true
\ No newline at end of file