From 72625833cdd003505f0f680b537c6638472f1658 Mon Sep 17 00:00:00 2001 From: yenon Date: Fri, 1 Dec 2023 18:39:18 +0100 Subject: [PATCH] Added additional abstraction for better squad overview. --- .idea/misc.xml | 1 - src/main/kotlin/Css.kt | 61 ++++++++++++++++------ src/main/kotlin/Main.kt | 41 ++++++++++++++- src/main/kotlin/OverviewTemplate.kt | 81 ++++++++++++++++++++--------- src/main/kotlin/RconAbstraction.kt | 76 +++++++++++++++++++++++---- 5 files changed, 206 insertions(+), 54 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 0363e93..e531707 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/src/main/kotlin/Css.kt b/src/main/kotlin/Css.kt index ec3278c..bcbf2a0 100644 --- a/src/main/kotlin/Css.kt +++ b/src/main/kotlin/Css.kt @@ -1,21 +1,48 @@ object Css { val cssString = """ - .teamView{ - height: 100%; - width: 100%; - display: flex; - } - .teamA{ - width: 50%; - } - .teamB{ - width: 50%; - } - .squadTable{ - width: 100%; - } - tr:nth-child(even) { - background-color: rgba(0, 0, 0, 0.5); - } +.teamView{ + height: 100%; + width: 100%; + display: flex; +} +.team{ + width: 50%; + margin: 6px; + padding: 6px; +} +.colorBluFor{ + background-color: #bbffff; +} +.colorRedFor{ + background-color: #ffbbbb; +} +.squad{ + background-color: #00000020; + margin: 6px; +} +.squadTable{ + width: 100%; +} +.squadHeader{ + background-color: #00000050 +} +.leader{ + background-color: #CD7F32 +} +.playerClass{ + width: 15%; +} +.playerName{ + width: 35%; +} +.playerId{ + width: 35%; +} +.playerButtons{ + width: 15%; +} +tr:nth-child(even) { + background-color: rgba(0, 0, 0, 0.1); +} """.trimIndent() } \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 3edf545..9c35bab 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -74,8 +74,47 @@ fun main() { } get("/overview") { val players = abstraction.getCurrentPlayers() + val squadInfo = abstraction.getSquadList() - call.respondHtmlTemplate(OverviewTemplate(players)) {} + call.respondHtmlTemplate(OverviewTemplate(players, squadInfo)) {} + } + get("/overviewDemo") { + //val players = abstraction.getCurrentPlayers() + //val squadInfo = abstraction.getSquadList() + val players = ListPlayersOutput( + arrayListOf( + Player(1, 86868686868686868L, "newSlMan", 1, 1, true, "SL"), + Player(2, 31337313373133711L, "IStayed(TM)", 2, 1, true, "SL"), + Player(1, 1L, "Man1", 1, 1, false, "RM"), + Player(1, 2L, "Man2", 1, 1, false, "LAT"), + Player(1, 3L, "Man3", 1, 1, false, "HAT"), + Player(1, 4L, "Man4", 1, 1, false, "MED"), + Player(1, 5L, "Man5", 1, 1, false, "GR"), + Player(1, 6L, "OtherSL1", 2, 2, true, "SL"), + Player(1, 7L, "OtherMan2", 2, 2, false, "MED"), + Player(1, 8L, "OtherMan3", 2, 0, false, "RM"), + Player(1, 9L, "OtherMan4", 2, 0, false, "RM"), + Player(1, 10L, "OtherMan5", 2, 0, false, "RM"), + ), arrayListOf() + ) + + val squadInfo = Pair( + RconAbstraction.SquadListFaction( + "1st Battalion, 1st Marines", hashMapOf( + 1 to RconAbstraction.SquadListSquad("HERPS", 5, true, "leaver9000", 69696969696969696L) + ) + ), + RconAbstraction.SquadListFaction( + "Insurgent Rebel Federation", hashMapOf( + 1 to RconAbstraction.SquadListSquad("DERPS", 1, true, "IStayed(TM)", 31337313373133711L), + 2 to RconAbstraction.SquadListSquad("NOOBS WELCOME", 2, false, "OtherSL1", 6L) + ) + ) + ) + + call.respondHtmlTemplate(OverviewTemplate(players, squadInfo)) { + + } } get("/css") { call.respondText(contentType = ContentType.Text.CSS, text = Css.cssString) diff --git a/src/main/kotlin/OverviewTemplate.kt b/src/main/kotlin/OverviewTemplate.kt index 8b0eb90..fdf873b 100644 --- a/src/main/kotlin/OverviewTemplate.kt +++ b/src/main/kotlin/OverviewTemplate.kt @@ -1,7 +1,10 @@ import io.ktor.server.html.* import kotlinx.html.* -class OverviewTemplate(private val players: ListPlayersOutput) : Template { +class OverviewTemplate( + private val players: ListPlayersOutput, + private val squadInfo: Pair +) : Template { private fun TR.makePlayer(player: Player) { td { +player.role } td { +player.name } @@ -11,7 +14,7 @@ class OverviewTemplate(private val players: ListPlayersOutput) : Template +"✉" } button {//Kick - +"\uD83E\uDDB6" + +"⏏" } button {//Ban +"⛔" @@ -19,38 +22,68 @@ class OverviewTemplate(private val players: ListPlayersOutput) : Template } } - private fun FlowContent.makeSquad(squad: Map.Entry>) { + private fun FlowContent.makeSquad( + squad: Map.Entry>, + squadListSquad: RconAbstraction.SquadListSquad + ) { val leader = squad.value.find { it.leader } - +if (squad.key != 0) { - "Squad ${squad.key}" - } else { - "Unassigned" - } - table("squadTable") { - leader?.let { - thead { - tr { - makePlayer(leader) - } + div("squad") { + div("squadHeader") { + if (squad.key != 0) { + +squadListSquad.name + } else { + +"Unassigned" + } + + if (squadListSquad.locked) { + +" \uD83D\uDD12" + } + + if (squadListSquad.creatorSteamId != (leader?.steamId ?: 0) && squadListSquad.creatorSteamId != 0L) { + +" (originally created by ${squadListSquad.creatorName} ${squadListSquad.creatorSteamId})" } } - squad.value.forEach { - if (!it.leader) { - tr { - makePlayer(it) + + table("squadTable") { + colGroup { + col("playerClass") + col("playerName") + col("playerId") + col("playerButtons") + } + leader?.let { + thead { + tr("leader") { + makePlayer(leader) + } + } + } + squad.value.forEach { + if (!it.leader) { + tr { + makePlayer(it) + } } } } } } - private fun FlowContent.makeSide(team: Map>) { + private fun FlowContent.makeSide(team: Map>, squadListFaction: RconAbstraction.SquadListFaction) { + div("teamHeader") { + +squadListFaction.name + } if (team.keys.isEmpty()) { +"empty" return } team.forEach { - makeSquad(it) + if (it.key != 0) { + makeSquad(it, squadListFaction.squads[it.key]!!) + } + } + team.filter { it.key == 0 }.forEach { + makeSquad(it, RconAbstraction.SquadListSquad("Unassigned", it.value.size, false, "default", 0L)) } } @@ -64,11 +97,11 @@ class OverviewTemplate(private val players: ListPlayersOutput) : Template body { div("teamView") { - div("teamA") { - makeSide(teamA) + div("team colorRedFor") { + makeSide(teamA, squadInfo.first) } - div("teamB") { - makeSide(teamB) + div("team colorBluFor") { + makeSide(teamB, squadInfo.second) } } } diff --git a/src/main/kotlin/RconAbstraction.kt b/src/main/kotlin/RconAbstraction.kt index fb42239..e50132a 100644 --- a/src/main/kotlin/RconAbstraction.kt +++ b/src/main/kotlin/RconAbstraction.kt @@ -1,11 +1,4 @@ -/* -Format for "ListPlayers" ------ Active Players ----- -ID: 0 | SteamID: 76561198037144702 | Name: [PACK] yenon | Team ID: 1 | Squad ID: N/A | Is Leader: False | Role: USA_Rifleman_01 ------ Recently Disconnected Players [Max of 15] ----- -ID: 1 | SteamID: 76561199074475884 | Since Disconnect: 03m.56s | Name: RoeveNn -ID: 2 | SteamID: 76561199262966526 | Since Disconnect: 03m.51s | Name: Aliko - */ + data class ListPlayersOutput(val players: ArrayList, val disconnected: ArrayList) data class Player( @@ -30,12 +23,19 @@ val inactivePlayerInListRegex = Regex( ) class RconAbstraction(private val connection: RconConnection) { + + /* + Format for "ListPlayers" + ----- Active Players ----- + ID: 0 | SteamID: 76561198037144702 | Name: [PACK] yenon | Team ID: 1 | Squad ID: N/A | Is Leader: False | Role: USA_Rifleman_01 + ----- Recently Disconnected Players [Max of 15] ----- + ID: 1 | SteamID: 76561199074475884 | Since Disconnect: 03m.56s | Name: RoeveNn + ID: 2 | SteamID: 76561199262966526 | Since Disconnect: 03m.51s | Name: Aliko + */ suspend fun getCurrentPlayers(): ListPlayersOutput { val active = arrayListOf() val disconnected = arrayListOf() connection.sendCommand("ListPlayers").getOrNull()?.let { - - println(it) activePlayerInListRegex.findAll(it).forEach { match -> val playerId = match.groupValues[1].toInt() val steamId = match.groupValues[2].toLong() @@ -57,6 +57,60 @@ class RconAbstraction(private val connection: RconConnection) { return ListPlayersOutput(active, disconnected) } + /* + ----- Active Squads ----- + Team ID: 1 (3rd Division) + ID: 1 | Name: HERPDERP2 THE RECONING | Size: 1 | Locked: True | Creator Name: yenon | Creator Steam ID: 76561198037144702 + Team ID: 2 (Insurgent Rebel Federation) + */ + + data class SquadListSquad( + val name: String, + val size: Int, + val locked: Boolean, + val creatorName: String, + val creatorSteamId: Long + ) + + data class SquadListFaction(val name: String, val squads: HashMap) + + suspend fun getSquadList(): Pair { + val lines = connection.sendCommand("ListSquads").getOrNull()!!.split("\n") + + val teamRegex = Regex("""^Team ID: ([12]) \(([^)]+)\)$""") + val squadRegex = + Regex("""^ID: (\d+) \| Name: (.*) \| Size: (\d+) \| Locked: (True|False) \| Creator Name: (.*) \| Creator Steam ID: (\d{17})${'$'}""") + + lateinit var faction1: SquadListFaction + lateinit var faction2: SquadListFaction + + lateinit var currentFaction: SquadListFaction + + lines.forEach { line -> + teamRegex.find(line)?.let { + if (it.groupValues[1].toInt() == 1) { + faction1 = SquadListFaction(it.groupValues[2], hashMapOf()) + currentFaction = faction1 + } else { + faction2 = SquadListFaction(it.groupValues[2], hashMapOf()) + currentFaction = faction2 + } + } + squadRegex.find(line)?.let { + val squadId = it.groupValues[1].toInt() + val name = it.groupValues[2] + val size = it.groupValues[3].toInt() + val locked = it.groupValues[4].lowercase().toBooleanStrict() + val creatorName = it.groupValues[5] + val creatorSteamId = it.groupValues[6].toLong() + + currentFaction.squads[squadId] = SquadListSquad(name, size, locked, creatorName, creatorSteamId) + } + } + + return Pair(faction1, faction2) + } + suspend fun kickPlayer(steamId: Long, reason: String) { connection.sendCommand("AdminKickById $steamId \"$reason\"") } @@ -66,6 +120,6 @@ class RconAbstraction(private val connection: RconConnection) { } suspend fun messagePlayer(steamId: Long, message: String) { - connection.sendCommand("AdminKickById $steamId \"$message\"") + connection.sendCommand("AdminMessageById $steamId \"$message\"") } } \ No newline at end of file