summaryrefslogtreewikicommitdiff
path: root/core/src/command
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2026-03-26 23:15:33 -0400
committerFreya Murphy <freya@freyacat.org>2026-03-27 23:09:23 -0400
commitf8322cd21cde68a72b05efbad3a05b8e67c0bdd0 (patch)
treed7e60bc8fedadc8fa7ae725571cad1f398eaf6dc /core/src/command
downloadkenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.tar.gz
kenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.tar.bz2
kenshinshideandseek2-f8322cd21cde68a72b05efbad3a05b8e67c0bdd0.zip
initial
Diffstat (limited to 'core/src/command')
-rw-r--r--core/src/command/Confirm.kt32
-rw-r--r--core/src/command/Debug.kt24
-rw-r--r--core/src/command/Help.kt39
-rw-r--r--core/src/command/Join.kt33
-rw-r--r--core/src/command/Leave.kt22
-rw-r--r--core/src/command/Reload.kt33
-rw-r--r--core/src/command/Send.kt30
-rw-r--r--core/src/command/SetExit.kt25
-rw-r--r--core/src/command/Start.kt34
-rw-r--r--core/src/command/Stop.kt27
-rw-r--r--core/src/command/Top.kt49
-rw-r--r--core/src/command/Wins.kt41
-rw-r--r--core/src/command/map/Add.kt35
-rw-r--r--core/src/command/map/GoTo.kt40
-rw-r--r--core/src/command/map/List.kt32
-rw-r--r--core/src/command/map/Remove.kt32
-rw-r--r--core/src/command/map/Save.kt31
-rw-r--r--core/src/command/map/Status.kt45
-rw-r--r--core/src/command/map/blockhunt/Debug.kt35
-rw-r--r--core/src/command/map/blockhunt/Enabled.kt39
-rw-r--r--core/src/command/map/blockhunt/block/Add.kt55
-rw-r--r--core/src/command/map/blockhunt/block/List.kt46
-rw-r--r--core/src/command/map/blockhunt/block/Remove.kt56
-rw-r--r--core/src/command/map/set/Border.kt64
-rw-r--r--core/src/command/map/set/Bounds.kt57
-rw-r--r--core/src/command/map/set/Lobby.kt37
-rw-r--r--core/src/command/map/set/SeekerLobby.kt38
-rw-r--r--core/src/command/map/set/Spawn.kt38
-rw-r--r--core/src/command/map/unset/Border.kt39
-rw-r--r--core/src/command/util/Command.kt17
-rw-r--r--core/src/command/util/CommandGroup.kt134
-rw-r--r--core/src/command/world/Create.kt40
-rw-r--r--core/src/command/world/Delete.kt50
-rw-r--r--core/src/command/world/List.kt42
-rw-r--r--core/src/command/world/Tp.kt36
35 files changed, 1427 insertions, 0 deletions
diff --git a/core/src/command/Confirm.kt b/core/src/command/Confirm.kt
new file mode 100644
index 0000000..359f490
--- /dev/null
+++ b/core/src/command/Confirm.kt
@@ -0,0 +1,32 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.REQUESTS
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+
+class KhsConfirm : Command {
+ override val label = "confirm"
+ override val usage = listOf<String>()
+ override val description = "Confirm a request of a previously run command"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val request = REQUESTS.remove(player.uuid)
+
+ if (request == null) {
+ player.message(plugin.locale.prefix.error + plugin.locale.confirm.none)
+ return
+ }
+
+ if (request.expired) {
+ player.message(plugin.locale.prefix.error + plugin.locale.confirm.timedOut)
+ return
+ }
+
+ request.fn()
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf()
+ }
+}
diff --git a/core/src/command/Debug.kt b/core/src/command/Debug.kt
new file mode 100644
index 0000000..d000d60
--- /dev/null
+++ b/core/src/command/Debug.kt
@@ -0,0 +1,24 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.inv.createDebugMenu
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsDebug : Command {
+ override val label = "debug"
+ override val usage = listOf<String>()
+ override val description = "Mess with/debug the current game"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ runChecks(plugin, player) { gameMapExists() }
+
+ val inv = createDebugMenu(plugin) ?: return
+ player.showInventory(inv)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf()
+ }
+}
diff --git a/core/src/command/Help.kt b/core/src/command/Help.kt
new file mode 100644
index 0000000..168b69c
--- /dev/null
+++ b/core/src/command/Help.kt
@@ -0,0 +1,39 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import kotlin.text.toUInt
+
+class KhsHelp : Command {
+ override val label = "help"
+ override val usage = listOf("*page")
+ override val description = "Lists the commands you can use"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val commands = plugin.commandGroup.commandsFor(player)
+ val pageSize = 4u
+ val pages = (commands.size.toUInt() + pageSize - 1u) / pageSize
+ val page = maxOf(minOf(args.firstOrNull().let { it?.toUIntOrNull() } ?: 0u, pages), 1u)
+
+ player.message(
+ buildString {
+ appendLine(
+ "&b=================== &fHelp: Page ($page/$pages) &b==================="
+ )
+ for ((label, command) in commands.chunked(pageSize.toInt()).get(page.toInt() - 1)) {
+ val cmd = label.substring(3)
+ val usage = command.usage.joinToString(" ")
+ val description = command.description
+ appendLine("&7?&f &b/hs &f$cmd &9$usage")
+ appendLine("&7?&f &7&o$description")
+ }
+ appendLine("&b=====================================================")
+ }
+ )
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf(parameter)
+ }
+}
diff --git a/core/src/command/Join.kt b/core/src/command/Join.kt
new file mode 100644
index 0000000..88bb94c
--- /dev/null
+++ b/core/src/command/Join.kt
@@ -0,0 +1,33 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsJoin : Command {
+ override val label = "join"
+ override val usage = listOf<String>("*map")
+ override val description = "Joins the game, and can set a map if the lobby is empty"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val mapName = args.firstOrNull()
+ val map = mapName?.let { plugin.maps.get(it) }
+
+ runChecks(plugin, player) {
+ gameMapExists()
+ playerNotInGame()
+ if (mapName != null) mapSetup(map)
+ }
+
+ if (plugin.game.size == 0u) plugin.game.setMap(map)
+
+ plugin.game.join(player.uuid)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "*map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/Leave.kt b/core/src/command/Leave.kt
new file mode 100644
index 0000000..752fae4
--- /dev/null
+++ b/core/src/command/Leave.kt
@@ -0,0 +1,22 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsLeave : Command {
+ override val label = "leave"
+ override val usage = listOf<String>()
+ override val description = "Leaves the game lobby"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ runChecks(plugin, player) { playerInGame() }
+
+ plugin.game.leave(player.uuid)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf()
+ }
+}
diff --git a/core/src/command/Reload.kt b/core/src/command/Reload.kt
new file mode 100644
index 0000000..eee5231
--- /dev/null
+++ b/core/src/command/Reload.kt
@@ -0,0 +1,33 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsReload : Command {
+ override val label = "reload"
+ override val usage = listOf<String>()
+ override val description = "Reload's the plugin config"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ runChecks(plugin, player) {
+ gameNotInProgress()
+ lobbyEmpty()
+ }
+
+ player.message(plugin.locale.prefix.default + plugin.locale.command.reloading)
+ plugin
+ .reloadConfig()
+ .onSuccess {
+ player.message(plugin.locale.prefix.default + plugin.locale.command.reloaded)
+ }
+ .onFailure {
+ player.message(plugin.locale.prefix.default + plugin.locale.command.errorReloading)
+ }
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf()
+ }
+}
diff --git a/core/src/command/Send.kt b/core/src/command/Send.kt
new file mode 100644
index 0000000..bca4467
--- /dev/null
+++ b/core/src/command/Send.kt
@@ -0,0 +1,30 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsSend : Command {
+ override val label = "send"
+ override val usage = listOf("map")
+ override val description = "Send the current lobby to another map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val map = plugin.maps.get(args.first())
+
+ runChecks(plugin, player) {
+ gameNotInProgress()
+ playerInGame()
+ mapSetup(map)
+ }
+
+ plugin.game.setMap(map)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/SetExit.kt b/core/src/command/SetExit.kt
new file mode 100644
index 0000000..d3feb2c
--- /dev/null
+++ b/core/src/command/SetExit.kt
@@ -0,0 +1,25 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsSetExit : Command {
+ override val label = "setexit"
+ override val usage = listOf<String>()
+ override val description = "Sets the plugins's exit location"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ runChecks(plugin, player) { gameNotInProgress() }
+
+ plugin.config.exit = player.location
+ plugin.saveConfig()
+
+ player.message(plugin.locale.prefix.default + plugin.locale.map.set.exit)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf()
+ }
+}
diff --git a/core/src/command/Start.kt b/core/src/command/Start.kt
new file mode 100644
index 0000000..e24c505
--- /dev/null
+++ b/core/src/command/Start.kt
@@ -0,0 +1,34 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsStart : Command {
+ override val label = "start"
+ override val usage = listOf("*seekers...")
+ override val description =
+ "Starts the game either with a random set of seekers or a chosen list"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ runChecks(plugin, player) {
+ gameMapExists()
+ gameNotInProgress()
+ playerInGame()
+ lobbyHasEnoughPlayers()
+ }
+
+ val pool =
+ args
+ .map { plugin.shim.getPlayer(it)?.uuid }
+ .filterNotNull()
+ .filter { plugin.game.hasPlayer(it) }
+
+ plugin.game.start(pool)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return plugin.game.players.map(Player::name)
+ }
+}
diff --git a/core/src/command/Stop.kt b/core/src/command/Stop.kt
new file mode 100644
index 0000000..c0ca401
--- /dev/null
+++ b/core/src/command/Stop.kt
@@ -0,0 +1,27 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.game.Game
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsStop : Command {
+ override val label = "stop"
+ override val usage = listOf<String>()
+ override val description = "Stops the game"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ runChecks(plugin, player) {
+ gameMapExists()
+ gameInProgress()
+ }
+
+ plugin.game.broadcast(plugin.locale.prefix.abort + plugin.locale.game.stop)
+ plugin.game.stop(Game.WinType.NONE)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf()
+ }
+}
diff --git a/core/src/command/Top.kt b/core/src/command/Top.kt
new file mode 100644
index 0000000..900f52f
--- /dev/null
+++ b/core/src/command/Top.kt
@@ -0,0 +1,49 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+
+class KhsTop : Command {
+ override val label = "top"
+ override val usage = listOf("*page")
+ override val description = "Shows the game leaderboard"
+
+ private fun getColor(index: UInt): Char {
+ return when (index) {
+ 0u -> 'e'
+ 1u -> '7'
+ 2u -> '6'
+ else -> 'f'
+ }
+ }
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ var page = args.firstOrNull()?.toUIntOrNull() ?: 0u
+ page = maxOf(page, 1u) - 1u
+
+ var pageSize = 5u
+ val entires = plugin.database?.getPlayers(page, pageSize)
+ if (entires == null || entires.isEmpty()) {
+ player.message(plugin.locale.prefix.default + plugin.locale.database.noInfo)
+ return
+ }
+
+ val message = buildString {
+ appendLine("&f------- &lLEADERBOARD &7(Page ${page + 1u}) &f-------")
+ for ((i, entry) in entires.withIndex()) {
+ val wins = entry.hiderWins + entry.seekerWins
+ val idx = (pageSize * page) + i.toUInt()
+ val color = getColor(idx)
+ val name = entry.name ?: continue
+ appendLine("&$color${idx + 1u}. &c$wins &f$name")
+ }
+ }
+
+ player.message(message)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf(parameter)
+ }
+}
diff --git a/core/src/command/Wins.kt b/core/src/command/Wins.kt
new file mode 100644
index 0000000..02faf04
--- /dev/null
+++ b/core/src/command/Wins.kt
@@ -0,0 +1,41 @@
+package cat.freya.khs.command
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+
+class KhsWins : Command {
+ override val label = "wins"
+ override val usage = listOf("player")
+ override val description = "Shows stats for a given player"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ val data = plugin.database?.getPlayer(name)
+ if (data == null) {
+ player.message(plugin.locale.prefix.default + plugin.locale.database.noInfo)
+ return
+ }
+
+ val message = buildString {
+ val wins = data.seekerWins + data.hiderWins
+ val games = wins + data.seekerLosses + data.hiderLosses
+ appendLine("&f&l" + "=".repeat(30))
+ appendLine(plugin.locale.database.infoFor.with(name))
+ appendLine("&bTOTAL WINS: &f$wins")
+ appendLine("&6HIDER WINS: &f${data.hiderWins}")
+ appendLine("&cSEEKER WINS: &f${data.seekerWins}")
+ appendLine("GAMES PLAYED: ${games}")
+ append("&f&l" + "=".repeat(30))
+ }
+
+ player.message(message)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return when (parameter) {
+ "player" -> plugin.database?.getPlayerNames(10u, typed) ?: listOf()
+ else -> listOf()
+ }
+ }
+}
diff --git a/core/src/command/map/Add.kt b/core/src/command/map/Add.kt
new file mode 100644
index 0000000..8505849
--- /dev/null
+++ b/core/src/command/map/Add.kt
@@ -0,0 +1,35 @@
+package cat.freya.khs.command.map
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.config.MapConfig
+import cat.freya.khs.game.KhsMap
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapAdd : Command {
+ override val label = "add"
+ override val usage = listOf("name", "world")
+ override val description = "Add a map to the plugin"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name, world) = args
+ runChecks(plugin, player) {
+ mapDoesNotExist(name)
+ mapNameValid(name)
+ worldValid(world)
+ }
+
+ plugin.maps[name] = KhsMap(name, MapConfig(world), plugin)
+ plugin.saveConfig()
+
+ player.message(plugin.locale.prefix.default + plugin.locale.map.created.with(name))
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "name" -> listOf("name")
+ "world" -> plugin.shim.worlds.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/GoTo.kt b/core/src/command/map/GoTo.kt
new file mode 100644
index 0000000..20444cc
--- /dev/null
+++ b/core/src/command/map/GoTo.kt
@@ -0,0 +1,40 @@
+package cat.freya.khs.command.map
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapGoTo : Command {
+ override val label = "goto"
+ override val usage = listOf("map", "spawn")
+ override val description = "Goes to a spawn location for a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name, spawn) = args
+ runChecks(plugin, player) { mapExists(name) }
+
+ var map = plugin.maps.get(name) ?: return
+ val loc =
+ when (spawn) {
+ "spawn" -> map.gameSpawn
+ "lobby" -> map.lobbySpawn
+ "seekerlobby" -> map.seekerLobbySpawn
+ else -> null
+ }
+
+ if (loc == null) {
+ player.message(plugin.locale.prefix.error + plugin.locale.map.error.locationNotSet)
+ return
+ }
+
+ loc.teleport(player)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ "spawn" -> listOf("spawn", "lobby", "seekerlobby").filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/List.kt b/core/src/command/map/List.kt
new file mode 100644
index 0000000..8bc7a81
--- /dev/null
+++ b/core/src/command/map/List.kt
@@ -0,0 +1,32 @@
+package cat.freya.khs.command.map
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+
+class KhsMapList : Command {
+ override val label = "list"
+ override val usage = listOf<String>()
+ override val description = "List maps known to the plugin"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ if (plugin.maps.isEmpty()) {
+ player.message(plugin.locale.prefix.default + plugin.locale.map.none)
+ return
+ }
+
+ player.message(
+ buildString {
+ appendLine(plugin.locale.prefix.default + plugin.locale.map.list)
+ for ((name, map) in plugin.maps) {
+ append("&e- &f$name: ")
+ appendLine(if (map.setup) "&aSETUP" else "&cNOT SETUP")
+ }
+ }
+ )
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf()
+ }
+}
diff --git a/core/src/command/map/Remove.kt b/core/src/command/map/Remove.kt
new file mode 100644
index 0000000..f8aab4f
--- /dev/null
+++ b/core/src/command/map/Remove.kt
@@ -0,0 +1,32 @@
+package cat.freya.khs.command.map
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapRemove : Command {
+ override val label = "remove"
+ override val usage = listOf("map")
+ override val description = "Remove a map from the plugin"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ mapExists(name)
+ gameNotInProgress()
+ lobbyEmpty()
+ }
+
+ plugin.maps.remove(name)
+ plugin.saveConfig()
+
+ player.message(plugin.locale.prefix.default + plugin.locale.map.deleted.with(name))
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/Save.kt b/core/src/command/map/Save.kt
new file mode 100644
index 0000000..a68b6cc
--- /dev/null
+++ b/core/src/command/map/Save.kt
@@ -0,0 +1,31 @@
+package cat.freya.khs.command.map
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.game.mapSave
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapSave : Command {
+ override val label = "save"
+ override val usage = listOf("map")
+ override val description = "Save the map backup used for gameplay"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ mapExists(name)
+ gameNotInProgress()
+ lobbyEmpty()
+ }
+
+ var map = plugin.maps.get(name) ?: return
+ mapSave(plugin, map)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/Status.kt b/core/src/command/map/Status.kt
new file mode 100644
index 0000000..596f306
--- /dev/null
+++ b/core/src/command/map/Status.kt
@@ -0,0 +1,45 @@
+package cat.freya.khs.command.map
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapStatus : Command {
+ override val label = "status"
+ override val usage = listOf("map")
+ override val description = "Says what is needed to fully setup the map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) { mapExists(name) }
+
+ val map = plugin.maps.get(name) ?: return
+
+ if (map.setup) {
+ player.message(plugin.locale.prefix.default + plugin.locale.map.setup.complete)
+ return
+ }
+
+ player.message(
+ buildString {
+ appendLine(plugin.locale.map.setup.header)
+ if (map.gameSpawn == null) appendLine(plugin.locale.map.setup.game)
+ if (map.lobbySpawn == null) appendLine(plugin.locale.map.setup.lobby)
+ if (map.seekerLobbySpawn == null) appendLine(plugin.locale.map.setup.seekerLobby)
+ if (plugin.config.exit == null) appendLine(plugin.locale.map.setup.exit)
+ if (map.bounds() == null) appendLine(plugin.locale.map.setup.bounds)
+ if (plugin.config.mapSaveEnabled && !map.hasMapSave())
+ appendLine(plugin.locale.map.setup.saveMap)
+ if (map.config.blockHunt.enabled && map.config.blockHunt.blocks.isEmpty())
+ appendLine(plugin.locale.map.setup.blockHunt)
+ }
+ )
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/blockhunt/Debug.kt b/core/src/command/map/blockhunt/Debug.kt
new file mode 100644
index 0000000..0620e3d
--- /dev/null
+++ b/core/src/command/map/blockhunt/Debug.kt
@@ -0,0 +1,35 @@
+package cat.freya.khs.command.map.blockhunt
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.inv.createBlockHuntPicker
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapBlockHuntDebug : Command {
+ override val label = "debug"
+ override val usage = listOf("map")
+ override val description = "Manually open the blockhunt picker for a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ blockHuntSupported()
+ blockHuntEnabled(name)
+ }
+
+ val map = plugin.maps.get(name) ?: return
+ val inv = createBlockHuntPicker(plugin, map) ?: return
+ player.showInventory(inv)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" ->
+ plugin.maps
+ .filter { it.value.config.blockHunt.enabled }
+ .map { it.key }
+ .filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/blockhunt/Enabled.kt b/core/src/command/map/blockhunt/Enabled.kt
new file mode 100644
index 0000000..a2ccdb7
--- /dev/null
+++ b/core/src/command/map/blockhunt/Enabled.kt
@@ -0,0 +1,39 @@
+package cat.freya.khs.command.map.blockhunt
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapBlockHuntEnabled : Command {
+ override val label = "enabled"
+ override val usage = listOf("map", "bool")
+ override val description = "Enable/disable blockhunt on a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name, enabled) = args
+ runChecks(plugin, player) {
+ blockHuntSupported()
+ mapExists(name)
+ gameNotInProgress()
+ }
+
+ val map = plugin.maps.get(name) ?: return
+ map.config.blockHunt.enabled = (enabled.lowercase() == "true")
+ map.reloadConfig()
+
+ val msg =
+ if (map.config.blockHunt.enabled) plugin.locale.blockHunt.enabled
+ else plugin.locale.blockHunt.disabled
+
+ plugin.saveConfig()
+ player.message(plugin.locale.prefix.default + msg)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ "bool" -> listOf("true", "false").filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/blockhunt/block/Add.kt b/core/src/command/map/blockhunt/block/Add.kt
new file mode 100644
index 0000000..6ed17be
--- /dev/null
+++ b/core/src/command/map/blockhunt/block/Add.kt
@@ -0,0 +1,55 @@
+package cat.freya.khs.command.map.blockhunt.block
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapBlockHuntBlockAdd : Command {
+ override val label = "add"
+ override val usage = listOf("map", "block")
+ override val description = "Add a block to a block hunt map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name, blockName) = args
+ runChecks(plugin, player) {
+ blockHuntSupported()
+ blockHuntEnabled(name)
+ gameNotInProgress()
+ lobbyEmpty()
+ }
+
+ val material = plugin.shim.parseMaterial(blockName)
+ if (material == null) {
+ player.message(plugin.locale.prefix.error + plugin.locale.blockHunt.block.unknown)
+ return
+ }
+
+ val map = plugin.maps.get(name) ?: return
+ if (map.config.blockHunt.blocks.contains(material)) {
+ player.message(
+ plugin.locale.prefix.error + plugin.locale.blockHunt.block.exists.with(material)
+ )
+ return
+ }
+
+ map.config.blockHunt.blocks += material
+ map.reloadConfig()
+
+ plugin.saveConfig()
+ player.message(
+ plugin.locale.prefix.default + plugin.locale.blockHunt.block.added.with(material)
+ )
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" ->
+ plugin.maps
+ .filter { it.value.config.blockHunt.enabled }
+ .map { it.key }
+ .filter { it.startsWith(typed) }
+ "block" -> listOf(parameter)
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/blockhunt/block/List.kt b/core/src/command/map/blockhunt/block/List.kt
new file mode 100644
index 0000000..b7df70d
--- /dev/null
+++ b/core/src/command/map/blockhunt/block/List.kt
@@ -0,0 +1,46 @@
+package cat.freya.khs.command.map.blockhunt.block
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapBlockHuntBlockList : Command {
+ override val label = "list"
+ override val usage = listOf("map")
+ override val description = "List blocks in use on a block hunt map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ blockHuntSupported()
+ blockHuntEnabled(name)
+ }
+
+ val map = plugin.maps.get(name) ?: return
+ val blocks = map.config.blockHunt.blocks
+ if (blocks.isEmpty()) {
+ player.message(plugin.locale.prefix.default + plugin.locale.blockHunt.block.none)
+ return
+ }
+
+ val message = buildString {
+ appendLine(plugin.locale.blockHunt.block.list)
+ for (block in blocks) {
+ appendLine("&e- &f$block")
+ }
+ }
+
+ player.message(message)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" ->
+ plugin.maps
+ .filter { it.value.config.blockHunt.enabled }
+ .map { it.key }
+ .filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/blockhunt/block/Remove.kt b/core/src/command/map/blockhunt/block/Remove.kt
new file mode 100644
index 0000000..3e81371
--- /dev/null
+++ b/core/src/command/map/blockhunt/block/Remove.kt
@@ -0,0 +1,56 @@
+package cat.freya.khs.command.map.blockhunt.block
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapBlockHuntBlockRemove : Command {
+ override val label = "remove"
+ override val usage = listOf("map", "block")
+ override val description = "Remove a block from a block hunt map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name, blockName) = args
+ runChecks(plugin, player) {
+ blockHuntSupported()
+ blockHuntEnabled(name)
+ gameNotInProgress()
+ lobbyEmpty()
+ }
+
+ val material = plugin.shim.parseMaterial(blockName)
+ if (material == null) {
+ player.message(plugin.locale.prefix.error + plugin.locale.blockHunt.block.unknown)
+ return
+ }
+
+ val map = plugin.maps.get(name) ?: return
+ if (!map.config.blockHunt.blocks.contains(material)) {
+ player.message(
+ plugin.locale.prefix.error +
+ plugin.locale.blockHunt.block.doesntExist.with(material)
+ )
+ return
+ }
+
+ map.config.blockHunt.blocks -= material
+ map.reloadConfig()
+
+ plugin.saveConfig()
+ player.message(
+ plugin.locale.prefix.default + plugin.locale.blockHunt.block.removed.with(material)
+ )
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" ->
+ plugin.maps
+ .filter { it.value.config.blockHunt.enabled }
+ .map { it.key }
+ .filter { it.startsWith(typed) }
+ "block" -> listOf(parameter)
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/set/Border.kt b/core/src/command/map/set/Border.kt
new file mode 100644
index 0000000..75a3f27
--- /dev/null
+++ b/core/src/command/map/set/Border.kt
@@ -0,0 +1,64 @@
+package cat.freya.khs.command.map.set
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapSetBorder : Command {
+ override val label = "border"
+ override val usage = listOf("map", "size", "delay", "move")
+ override val description = "Enable the world border for a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name, sizeS, delayS, moveS) = args
+ runChecks(plugin, player) {
+ mapExists(name)
+ inMapWorld(name)
+ gameNotInProgress()
+ }
+
+ val size = sizeS.toULong()
+ val delay = delayS.toULong()
+ val move = moveS.toULong()
+
+ if (size < 100u) {
+ player.message(plugin.locale.prefix.error + plugin.locale.worldBorder.minSize)
+ return
+ }
+
+ if (move < 1u) {
+ player.message(plugin.locale.prefix.error + plugin.locale.worldBorder.minChange)
+ return
+ }
+
+ var map = plugin.maps.get(name) ?: return
+ val config = map.config.worldBorder
+ config.enabled = true
+ config.pos = player.location.position
+ config.size = size
+ config.delay = delay
+ config.move = move
+
+ runChecks(plugin, player) {
+ // note this is not error, only warn
+ spawnsInRange(map)
+ }
+
+ map.reloadConfig()
+
+ plugin.saveConfig()
+ player.message(
+ plugin.locale.prefix.default + plugin.locale.worldBorder.enable.with(size, delay, move)
+ )
+
+ val loc = player.location.position
+ map.world?.border?.move(loc.x, loc.z, size, 0UL)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf(parameter)
+ }
+}
diff --git a/core/src/command/map/set/Bounds.kt b/core/src/command/map/set/Bounds.kt
new file mode 100644
index 0000000..7c13802
--- /dev/null
+++ b/core/src/command/map/set/Bounds.kt
@@ -0,0 +1,57 @@
+package cat.freya.khs.command.map.set
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.config.BoundConfig
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapSetBounds : Command {
+ override val label = "bounds"
+ override val usage = listOf("map")
+ override val description = "Sets the map bounds for a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ mapExists(name)
+ inMapWorld(name)
+ gameNotInProgress()
+ }
+
+ var map = plugin.maps.get(name) ?: return
+ val config = map.config.bounds
+
+ val pos = player.location.position
+ val num: Int
+
+ if (config.min == null || config.max != null) {
+ config.min = BoundConfig(pos.x, pos.z)
+ config.max == null
+ num = 1
+ } else {
+ val minX = minOf(config.min?.x ?: 0.0, pos.x)
+ val minZ = minOf(config.min?.z ?: 0.0, pos.z)
+ val maxX = maxOf(config.min?.x ?: 0.0, pos.x)
+ val maxZ = maxOf(config.min?.z ?: 0.0, pos.z)
+ config.min = BoundConfig(minX, minZ)
+ config.max = BoundConfig(maxX, maxZ)
+ num = 2
+ }
+
+ runChecks(plugin, player) {
+ // note this is not error, only warn
+ spawnsInRange(map)
+ }
+
+ map.reloadConfig()
+ plugin.saveConfig()
+ player.message(plugin.locale.prefix.default + plugin.locale.map.set.bounds.with(num))
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/set/Lobby.kt b/core/src/command/map/set/Lobby.kt
new file mode 100644
index 0000000..a90259a
--- /dev/null
+++ b/core/src/command/map/set/Lobby.kt
@@ -0,0 +1,37 @@
+package cat.freya.khs.command.map.set
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapSetLobby : Command {
+ override val label = "lobby"
+ override val usage = listOf("map")
+ override val description = "Sets the lobby spawn location for a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ mapExists(name)
+ inMapWorld(name)
+ gameNotInProgress()
+ }
+
+ var map = plugin.maps.get(name) ?: return
+ val pos = player.location.position
+
+ runChecks(plugin, player) { spawnInRange(map, pos) }
+
+ map.config.spawns.lobby = pos
+ map.reloadConfig()
+ plugin.saveConfig()
+ player.message(plugin.locale.prefix.default + plugin.locale.map.set.lobby)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/set/SeekerLobby.kt b/core/src/command/map/set/SeekerLobby.kt
new file mode 100644
index 0000000..71122cb
--- /dev/null
+++ b/core/src/command/map/set/SeekerLobby.kt
@@ -0,0 +1,38 @@
+package cat.freya.khs.command.map.set
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapSetSeekerLobby : Command {
+ override val label = "seekerlobby"
+ override val usage = listOf("map")
+ override val description = "Sets the seeker lobby spawn location for a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ mapExists(name)
+ inMapWorld(name)
+ gameNotInProgress()
+ }
+
+ var map = plugin.maps.get(name) ?: return
+ val pos = player.location.position
+
+ runChecks(plugin, player) { spawnInRange(map, pos) }
+
+ map.config.spawns.seeker = pos
+ map.reloadConfig()
+
+ plugin.saveConfig()
+ player.message(plugin.locale.prefix.default + plugin.locale.map.set.seekerSpawn)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/set/Spawn.kt b/core/src/command/map/set/Spawn.kt
new file mode 100644
index 0000000..4eff730
--- /dev/null
+++ b/core/src/command/map/set/Spawn.kt
@@ -0,0 +1,38 @@
+package cat.freya.khs.command.map.set
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapSetSpawn : Command {
+ override val label = "spawn"
+ override val usage = listOf("map")
+ override val description = "Sets the game spawn location for a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ mapExists(name)
+ inMapWorld(name)
+ gameNotInProgress()
+ }
+
+ var map = plugin.maps.get(name) ?: return
+ val pos = player.location.position
+
+ runChecks(plugin, player) { spawnInRange(map, pos) }
+
+ map.config.spawns.game = pos.toLegacy()
+ map.reloadConfig()
+
+ plugin.saveConfig()
+ player.message(plugin.locale.prefix.default + plugin.locale.map.set.gameSpawn)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/map/unset/Border.kt b/core/src/command/map/unset/Border.kt
new file mode 100644
index 0000000..87e7b85
--- /dev/null
+++ b/core/src/command/map/unset/Border.kt
@@ -0,0 +1,39 @@
+package cat.freya.khs.command.map.unset
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsMapUnsetBorder : Command {
+ override val label = "border"
+ override val usage = listOf("map")
+ override val description = "Disable the world border for a map"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ mapExists(name)
+ gameNotInProgress()
+ }
+
+ var map = plugin.maps.get(name) ?: return
+ val config = map.config.worldBorder
+ config.enabled = false
+ config.pos = null
+ config.size = null
+ config.delay = null
+ config.move = null
+
+ plugin.saveConfig()
+ player.message(plugin.locale.prefix.default + plugin.locale.worldBorder.disable)
+
+ map.world?.border?.move(0.0, 0.0, 30_000_000UL, 0UL)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> =
+ when (parameter) {
+ "map" -> plugin.maps.keys.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+}
diff --git a/core/src/command/util/Command.kt b/core/src/command/util/Command.kt
new file mode 100644
index 0000000..734305a
--- /dev/null
+++ b/core/src/command/util/Command.kt
@@ -0,0 +1,17 @@
+package cat.freya.khs.command.util
+
+import cat.freya.khs.Khs
+import cat.freya.khs.player.Player
+
+interface CommandPart {
+ val label: String
+}
+
+interface Command : CommandPart {
+ val usage: List<String>
+ val description: String
+
+ fun execute(plugin: Khs, player: Player, args: List<String>)
+
+ fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String>
+}
diff --git a/core/src/command/util/CommandGroup.kt b/core/src/command/util/CommandGroup.kt
new file mode 100644
index 0000000..72659d5
--- /dev/null
+++ b/core/src/command/util/CommandGroup.kt
@@ -0,0 +1,134 @@
+package cat.freya.khs.command.util
+
+import cat.freya.khs.Khs
+import cat.freya.khs.player.Player
+
+private data class CommandData(val command: Command, val permission: String, val args: List<String>)
+
+class CommandGroup(val plugin: Khs, override val label: String, vararg commands: CommandPart) :
+ CommandPart {
+ // set of commands to run in this group
+ private final val REGISTRY: Map<String, CommandPart> =
+ commands.associate { it.label.lowercase() to it }
+
+ private fun getCommand(args: List<String>, permission: String): CommandData? {
+ val invoke = args.firstOrNull()?.lowercase() ?: return null
+ val command = REGISTRY.get(invoke) ?: return null
+
+ return when (command) {
+ is Command -> CommandData(command, "$permission.$invoke", args.drop(1))
+ is CommandGroup -> command.getCommand(args.drop(1), "$permission.$invoke")
+ else -> null
+ }
+ }
+
+ private fun messageAbout(player: Player) {
+ val version = plugin.shim.pluginVersion
+ player.message(
+ "&b&lKenshin's Hide and Seek &7(&f$version&7)\n" +
+ "&7Author: &f[KenshinEto]\n" +
+ "&7Help Command: &b/hs &fhelp"
+ )
+ }
+
+ fun handleCommand(player: Player, args: List<String>) {
+ val data = getCommand(args, label) ?: return messageAbout(player)
+
+ if (plugin.saving) {
+ player.message(plugin.locale.prefix.error + plugin.locale.command.notAllowedTemp)
+ return
+ }
+
+ if (plugin.config.permissionsRequired && !player.hasPermission(data.permission)) {
+ player.message(plugin.locale.prefix.error + plugin.locale.command.notAllowed)
+ return
+ }
+
+ val paramCount = data.command.usage.filter { it.firstOrNull() != '*' }.count()
+ if (data.args.size < paramCount) {
+ player.message(plugin.locale.prefix.error + plugin.locale.command.notEnoughArguments)
+ return
+ }
+
+ runCatching { data.command.execute(plugin, player, data.args) }
+ .onFailure {
+ player.message(
+ plugin.locale.prefix.error + (it.message ?: plugin.locale.command.unknownError)
+ )
+
+ if (plugin.config.debug) {
+ plugin.shim.logger.warning("=== KHS BEGIN DEBUG TRACE ===")
+ plugin.shim.logger.warning(it.stackTraceToString())
+ plugin.shim.logger.warning("=== KHS END DEBUG TRACE ===")
+ }
+ }
+ }
+
+ private fun handleTabComplete(
+ player: Player,
+ args: List<String>,
+ permission: String,
+ ): List<String> {
+ val invoke = args.firstOrNull()?.lowercase() ?: return listOf()
+ val command = REGISTRY.get(invoke)
+ return when {
+ command is Command -> {
+ if (
+ plugin.config.permissionsRequired &&
+ !player.hasPermission("$permission.$invoke")
+ )
+ return listOf()
+
+ var index = maxOf(args.size - 1, 1)
+ val typed = args.getOrNull(index) ?: return listOf()
+
+ // handle last argument of usage being a varadic (...)
+ if (
+ index >= command.usage.size &&
+ command.usage.lastOrNull()?.endsWith("...") == true
+ )
+ index = command.usage.size
+
+ val parameter = command.usage.getOrNull(index - 1) ?: return listOf()
+
+ command.autoComplete(plugin, parameter, typed)
+ }
+ command is CommandGroup ->
+ command.handleTabComplete(player, args.drop(1), "$permission.$invoke")
+ args.size == 1 -> REGISTRY.keys.filter { it.startsWith(invoke) }
+ else -> listOf()
+ }
+ }
+
+ fun handleTabComplete(player: Player, args: List<String>): List<String> {
+ return handleTabComplete(player, args, label)
+ }
+
+ private fun commandsFor(
+ player: Player,
+ label: String,
+ permission: String,
+ res: MutableList<Pair<String, Command>>,
+ ) {
+ for ((invoke, command) in REGISTRY) {
+ when (command) {
+ is Command -> {
+ if (
+ plugin.config.permissionsRequired &&
+ !player.hasPermission("$permission.$invoke")
+ )
+ continue
+ res.add("$label $invoke" to command)
+ }
+ is CommandGroup ->
+ command.commandsFor(player, "$label $invoke", "$permission.$invoke", res)
+ }
+ }
+ }
+
+ fun commandsFor(player: Player): List<Pair<String, Command>> {
+ val commands = mutableListOf<Pair<String, Command>>()
+ commandsFor(player, label, label, commands)
+ return commands
+ }
+}
diff --git a/core/src/command/world/Create.kt b/core/src/command/world/Create.kt
new file mode 100644
index 0000000..2deece7
--- /dev/null
+++ b/core/src/command/world/Create.kt
@@ -0,0 +1,40 @@
+package cat.freya.khs.command.world
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+import cat.freya.khs.world.World
+
+class KhsWorldCreate : Command {
+ override val label = "create"
+ override val usage = listOf("name", "type")
+ override val description = "Create a new world"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name, typeStr) = args
+ runChecks(plugin, player) { worldDoesNotExist(name) }
+
+ val type =
+ World.Type.values().find { it.name.lowercase() == typeStr.lowercase() }
+ ?: World.Type.NORMAL
+
+ val world = plugin.shim.createWorld(name, type)
+ if (world == null) {
+ player.message(plugin.locale.prefix.error + plugin.locale.world.addedFailed.with(name))
+ return
+ }
+
+ player.teleport(world.spawn.withWorld(name))
+ player.message(plugin.locale.prefix.default + plugin.locale.world.added.with(name))
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return when (parameter) {
+ "name" -> listOf(parameter)
+ "type" ->
+ World.Type.values().map { it.name.lowercase() }.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+ }
+}
diff --git a/core/src/command/world/Delete.kt b/core/src/command/world/Delete.kt
new file mode 100644
index 0000000..64710a6
--- /dev/null
+++ b/core/src/command/world/Delete.kt
@@ -0,0 +1,50 @@
+package cat.freya.khs.command.world
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+import java.io.File
+
+class KhsWorldDelete : Command {
+ override val label = "delete"
+ override val usage = listOf("name")
+ override val description = "Delete an existing world"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) {
+ worldExists(name)
+ worldNotInUse(name)
+ }
+
+ val loader = plugin.shim.getWorldLoader(name)
+
+ // sanity check
+ // for the love of god, make sure were rm -fr'ing a world, not like
+ // some ones home dir ;-;
+ val lock = File(loader.dir, "session.lock")
+ val data = File(loader.dir, "level.dat")
+ if (!lock.exists() || !data.exists()) {
+ player.message(plugin.locale.prefix.error + plugin.locale.world.doesntExist.with(name))
+ return
+ }
+
+ loader.unload()
+ if (!loader.dir.deleteRecursively()) {
+ player.message(
+ plugin.locale.prefix.error + plugin.locale.world.removedFailed.with(name)
+ )
+ return
+ }
+
+ player.message(plugin.locale.prefix.default + plugin.locale.world.removed.with(name))
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return when (parameter) {
+ "name" -> plugin.shim.worlds.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+ }
+}
diff --git a/core/src/command/world/List.kt b/core/src/command/world/List.kt
new file mode 100644
index 0000000..af48f03
--- /dev/null
+++ b/core/src/command/world/List.kt
@@ -0,0 +1,42 @@
+package cat.freya.khs.command.world
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.world.World
+
+class KhsWorldList : Command {
+ override val label = "list"
+ override val usage = listOf<String>()
+ override val description = "Teleport to a world's spawn"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val worlds = plugin.shim.worlds
+ if (worlds.isEmpty()) {
+ // uhhh, we have to be in a world to call this 0_0
+ player.message(plugin.locale.prefix.error + plugin.locale.world.none)
+ return
+ }
+
+ val message = buildString {
+ appendLine(plugin.locale.world.list)
+ for (worldName in worlds) {
+ val world = plugin.shim.getWorld(worldName)
+ val status =
+ when (world?.type) {
+ World.Type.NORMAL -> "&aNORMAL"
+ World.Type.FLAT -> "&aFLAT"
+ World.Type.NETHER -> "&cNETHER"
+ World.Type.END -> "&eEND"
+ else -> "&7NOT LOADED"
+ }
+ appendLine("&e- &f$worldName: $status")
+ }
+ }
+ player.message(message)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return listOf()
+ }
+}
diff --git a/core/src/command/world/Tp.kt b/core/src/command/world/Tp.kt
new file mode 100644
index 0000000..c103a0c
--- /dev/null
+++ b/core/src/command/world/Tp.kt
@@ -0,0 +1,36 @@
+package cat.freya.khs.command.world
+
+import cat.freya.khs.Khs
+import cat.freya.khs.command.util.Command
+import cat.freya.khs.player.Player
+import cat.freya.khs.runChecks
+
+class KhsWorldTp : Command {
+ override val label = "tp"
+ override val usage = listOf("name")
+ override val description = "Teleport to a world's spawn"
+
+ override fun execute(plugin: Khs, player: Player, args: List<String>) {
+ val (name) = args
+ runChecks(plugin, player) { worldExists(name) }
+
+ val loader = plugin.shim.getWorldLoader(name)
+ loader.load()
+
+ val world = plugin.shim.getWorld(name)
+ if (world == null) {
+ player.message(plugin.locale.prefix.error + plugin.locale.world.loadFailed.with(name))
+ return
+ }
+
+ val spawn = world.spawn.withWorld(name)
+ player.teleport(spawn)
+ }
+
+ override fun autoComplete(plugin: Khs, parameter: String, typed: String): List<String> {
+ return when (parameter) {
+ "name" -> plugin.shim.worlds.filter { it.startsWith(typed) }
+ else -> listOf()
+ }
+ }
+}