Add tablist manager and Volcano rank provider
Introduce a TablistManager to manage per-player scoreboard teams, tab header/footer and periodic updates; uses a ServerRankProvider abstraction. Add DefaultServerRankProvider and VolcanoServerRankProvider (uses CoreAPI) in ServerRankProvider.kt. Wire TablistManager into SpeedHG: initialize in onLoad, update on join, and shutdown on disable. Add local VolcanoAPI.jar as compileOnly dependency and declare Volcano as a plugin dependency in plugin.yml.
This commit is contained in:
@@ -33,6 +33,8 @@ dependencies {
|
|||||||
compileOnly("com.lunarclient:apollo-api:1.2.4")
|
compileOnly("com.lunarclient:apollo-api:1.2.4")
|
||||||
compileOnly("com.lunarclient:apollo-extra-adventure4:1.2.4")
|
compileOnly("com.lunarclient:apollo-extra-adventure4:1.2.4")
|
||||||
|
|
||||||
|
compileOnly(files( "${rootProject.projectDir}/libs/VolcanoAPI.jar" ))
|
||||||
|
|
||||||
compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT")
|
compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT")
|
||||||
compileOnly("com.sk89q.worldedit:worldedit-core:7.2.17-SNAPSHOT")
|
compileOnly("com.sk89q.worldedit:worldedit-core:7.2.17-SNAPSHOT")
|
||||||
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.17-SNAPSHOT")
|
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.17-SNAPSHOT")
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ import club.mcscrims.speedhg.perk.impl.VampirePerk
|
|||||||
import club.mcscrims.speedhg.perk.listener.PerkEventDispatcher
|
import club.mcscrims.speedhg.perk.listener.PerkEventDispatcher
|
||||||
import club.mcscrims.speedhg.ranking.RankingManager
|
import club.mcscrims.speedhg.ranking.RankingManager
|
||||||
import club.mcscrims.speedhg.scoreboard.ScoreboardManager
|
import club.mcscrims.speedhg.scoreboard.ScoreboardManager
|
||||||
|
import club.mcscrims.speedhg.scoreboard.TablistManager
|
||||||
|
import club.mcscrims.speedhg.scoreboard.VolcanoServerRankProvider
|
||||||
import club.mcscrims.speedhg.team.TeamListener
|
import club.mcscrims.speedhg.team.TeamListener
|
||||||
import club.mcscrims.speedhg.team.TeamManager
|
import club.mcscrims.speedhg.team.TeamManager
|
||||||
import club.mcscrims.speedhg.webhook.DiscordWebhookManager
|
import club.mcscrims.speedhg.webhook.DiscordWebhookManager
|
||||||
@@ -114,6 +116,9 @@ class SpeedHG : JavaPlugin() {
|
|||||||
lateinit var lobbyItemManager: LobbyItemManager
|
lateinit var lobbyItemManager: LobbyItemManager
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
lateinit var tablistManager: TablistManager
|
||||||
|
private set
|
||||||
|
|
||||||
override fun onLoad()
|
override fun onLoad()
|
||||||
{
|
{
|
||||||
instance = this
|
instance = this
|
||||||
@@ -169,6 +174,7 @@ class SpeedHG : JavaPlugin() {
|
|||||||
discordWebhookManager = DiscordWebhookManager( this )
|
discordWebhookManager = DiscordWebhookManager( this )
|
||||||
lunarClientManager = LunarClientManager( this )
|
lunarClientManager = LunarClientManager( this )
|
||||||
lobbyItemManager = LobbyItemManager( this )
|
lobbyItemManager = LobbyItemManager( this )
|
||||||
|
tablistManager = TablistManager( this, VolcanoServerRankProvider() )
|
||||||
|
|
||||||
perkManager = PerkManager( this )
|
perkManager = PerkManager( this )
|
||||||
perkManager.initialize()
|
perkManager.initialize()
|
||||||
@@ -197,6 +203,7 @@ class SpeedHG : JavaPlugin() {
|
|||||||
podiumManager.cleanup()
|
podiumManager.cleanup()
|
||||||
if ( ::perkManager.isInitialized ) perkManager.shutdown()
|
if ( ::perkManager.isInitialized ) perkManager.shutdown()
|
||||||
if ( ::teamManager.isInitialized ) teamManager.reset()
|
if ( ::teamManager.isInitialized ) teamManager.reset()
|
||||||
|
if ( ::tablistManager.isInitialized ) tablistManager.shutdown()
|
||||||
if ( ::statsManager.isInitialized ) statsManager.shutdown()
|
if ( ::statsManager.isInitialized ) statsManager.shutdown()
|
||||||
if ( ::databaseManager.isInitialized ) databaseManager.disconnect()
|
if ( ::databaseManager.isInitialized ) databaseManager.disconnect()
|
||||||
if ( ::dataPackManager.isInitialized ) dataPackManager.uninstall()
|
if ( ::dataPackManager.isInitialized ) dataPackManager.uninstall()
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package club.mcscrims.speedhg.scoreboard
|
||||||
|
|
||||||
|
import me.zowpy.core.api.CoreAPI
|
||||||
|
import me.zowpy.core.api.rank.Rank
|
||||||
|
import net.kyori.adventure.text.Component
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert Server-Rang-Informationen für den [TablistManager]
|
||||||
|
*/
|
||||||
|
interface ServerRankProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den logischen Rang-Schlüssel zurück, der auf ein Sort-Gewicht
|
||||||
|
* in [TablistManager.WEIGHT_MAP] zeigt.
|
||||||
|
* Muss einer der Keys sein: `"admin"`, `"mod"`, `"helper"`, `"player"`.
|
||||||
|
*/
|
||||||
|
fun getRankWeight( player: Player ): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die formatierte Prefix-Komponente zurück, die VOR dem
|
||||||
|
* Spielernamen in der Tabliste erscheint.
|
||||||
|
*
|
||||||
|
* Beispiel: `<red><bold>[Admin]</bold></red> ` (mit Leerzeichen am Ende).
|
||||||
|
* Gibt [Component.empty] zurück wenn kein Prefix gewünscht.
|
||||||
|
*/
|
||||||
|
fun getRankPrefix( player: Player ): Component
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt einen MiniMessage-Farb-Tag zurück, der auf den Spielernamen
|
||||||
|
* in der Namens-Spalte angewendet wird.
|
||||||
|
* Beispiel: `"<red>"` für Admins, `"<gray>"` für normale Spieler.
|
||||||
|
*/
|
||||||
|
fun getRankColor( player: Player ): String
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Einfache Implementierung auf Basis von Bukkit-Permissions.
|
||||||
|
*/
|
||||||
|
class DefaultServerRankProvider : ServerRankProvider {
|
||||||
|
|
||||||
|
private val mm = MiniMessage.miniMessage()
|
||||||
|
|
||||||
|
override fun getRankWeight(
|
||||||
|
player: Player
|
||||||
|
): String = when {
|
||||||
|
player.hasPermission( "group.admin" ) -> "admin"
|
||||||
|
player.hasPermission( "group.mod" ) -> "mod"
|
||||||
|
player.hasPermission( "group.helper" ) -> "helper"
|
||||||
|
else -> "player"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRankPrefix(
|
||||||
|
player: Player
|
||||||
|
): Component = when {
|
||||||
|
player.hasPermission( "group.admin" ) -> mm.deserialize( "<red><bold>[Admin]</bold></red>" )
|
||||||
|
player.hasPermission( "group.mod" ) -> mm.deserialize( "<green><bold>[Mod]</bold></green>" )
|
||||||
|
player.hasPermission( "group.helper" ) -> mm.deserialize( "<aqua><bold>[Helper]</bold></aqua>" )
|
||||||
|
else -> Component.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRankColor(
|
||||||
|
player: Player
|
||||||
|
): String = when {
|
||||||
|
player.hasPermission( "group.admin" ) -> "<red>"
|
||||||
|
player.hasPermission( "group.mod" ) -> "<green>"
|
||||||
|
player.hasPermission( "group.helper" ) -> "<aqua>"
|
||||||
|
else -> "<gray>"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Volcano Implementierung
|
||||||
|
*/
|
||||||
|
class VolcanoServerRankProvider : ServerRankProvider {
|
||||||
|
|
||||||
|
private val coreAPI get() = CoreAPI.getInstance()
|
||||||
|
private val mm = MiniMessage.miniMessage()
|
||||||
|
|
||||||
|
override fun getRankWeight(
|
||||||
|
player: Player
|
||||||
|
): String
|
||||||
|
{
|
||||||
|
return getRank( player ).weight.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRankPrefix(
|
||||||
|
player: Player
|
||||||
|
): Component
|
||||||
|
{
|
||||||
|
return mm.deserialize(getRank( player ).prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRankColor(
|
||||||
|
player: Player
|
||||||
|
): String
|
||||||
|
{
|
||||||
|
return getRank( player ).displayColor
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRank(
|
||||||
|
player: Player
|
||||||
|
): Rank
|
||||||
|
{
|
||||||
|
val rank = coreAPI.profileManager.getByUUID( player.uniqueId ).realRank
|
||||||
|
return coreAPI.rankManager.getByUUID( rank.uuid )
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
package club.mcscrims.speedhg.scoreboard
|
||||||
|
|
||||||
|
import club.mcscrims.speedhg.SpeedHG
|
||||||
|
import club.mcscrims.speedhg.ranking.Rank
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||||
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.EventPriority
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent
|
||||||
|
import org.bukkit.scheduler.BukkitTask
|
||||||
|
import org.bukkit.scoreboard.Scoreboard
|
||||||
|
import org.bukkit.scoreboard.Team
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verwaltet Sortierung, Prefix/Suffix und Header/Footer der Spieler-Tabliste.
|
||||||
|
*
|
||||||
|
* ## Sortier-Mechanismus (Scoreboard-Teams)
|
||||||
|
*
|
||||||
|
* Minecraft sortiert Tab-Einträge **alphabetisch nach Team-Namen**, dann nach
|
||||||
|
* Spielername innerhalb des Teams. Wir nutzen das aus:
|
||||||
|
*
|
||||||
|
* | Rang | Team-Name | Sortierung |
|
||||||
|
* |---------|------------------------|-----------|
|
||||||
|
* | Admin | `sHG_00_AdminBob` | ganz oben |
|
||||||
|
* | Mod | `sHG_01_CoolMod` | ↓ |
|
||||||
|
* | Helper | `sHG_02_HelperAnna` | ↓ |
|
||||||
|
* | Spieler | `sHG_99_Notch` | unten |
|
||||||
|
*
|
||||||
|
* Da `"0" < "9"` alphabetisch gilt, landen Admins immer vor Spielern.
|
||||||
|
*
|
||||||
|
* ## Warum ein Team pro Spieler?
|
||||||
|
* Scoreboard-Teams besitzen **eine** Prefix/Suffix-Einstellung für alle
|
||||||
|
* Mitglieder. Da jeder Spieler einen individuellen SpeedHG-Rang als Suffix
|
||||||
|
* benötigt (z. B. `[Gold II]` vs. `[Silver III]`), erhält jeder Spieler
|
||||||
|
* sein eigenes Team. Das finale Tab-Format sieht so aus:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* [team.prefix()] [playerListName] [team.suffix()]
|
||||||
|
* [Admin] Notch [Gold II]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Scoreboard-Isolation
|
||||||
|
* Es wird ein dediziertes [Scoreboard] verwendet, das nie mit FastBoard
|
||||||
|
* geteilt wird. FastBoard 2.x sendet Sidebar-Pakete direkt — es liest
|
||||||
|
* `player.scoreboard` nicht aus, daher gibt es keinen Konflikt.
|
||||||
|
*
|
||||||
|
* ## Rang-Anbindung
|
||||||
|
* Implementiere [ServerRankProvider] für LuckPerms oder einen anderen
|
||||||
|
* Permission-Manager und übergib ihn im Konstruktor.
|
||||||
|
*/
|
||||||
|
class TablistManager(
|
||||||
|
private val plugin: SpeedHG,
|
||||||
|
val rankProvider: ServerRankProvider = DefaultServerRankProvider()
|
||||||
|
) : Listener {
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Konstanten
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Logischer Rang-Key -> numerisches Sort-Gewicht
|
||||||
|
* Das Gewicht wird Teil des Team-Namens: `sHG_{gewicht}_{spielerName}`
|
||||||
|
*
|
||||||
|
* Neue Ränge einfach hier eintragen, z.B. `"vip"` to "05"
|
||||||
|
*/
|
||||||
|
val WEIGHT_MAP: Map<String, String> = linkedMapOf(
|
||||||
|
"admin" to "00",
|
||||||
|
"mod" to "01",
|
||||||
|
"helper" to "02",
|
||||||
|
"vip" to "10",
|
||||||
|
"player" to "99"
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Namespace-Präfix für alle Tab-Teams — verhindert Namenskollisionen. */
|
||||||
|
private const val TEAM_NAMESPACE = "sHG_"
|
||||||
|
|
||||||
|
/** Ticks zwischen periodischen Aktualisierungen (60 = 3 Sekunden). */
|
||||||
|
private const val UPDATE_INTERVAL_TICKS = 60L
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Zustand
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private val mm = MiniMessage.miniMessage()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dediziertes Scoreboard ausschließlich für das Tab-Management.
|
||||||
|
* Niemals mit anderen Systemen teilen.
|
||||||
|
*/
|
||||||
|
private val scoreboard: Scoreboard =
|
||||||
|
requireNotNull( Bukkit.getScoreboardManager() ).newScoreboard
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UUID → aktueller Team-Name des Spielers.
|
||||||
|
* Notwendig für sauberes Aufräumen bei Rang-Wechsel oder Disconnect.
|
||||||
|
*/
|
||||||
|
private val playerTeams = ConcurrentHashMap<UUID, String>()
|
||||||
|
|
||||||
|
private var updateTask: BukkitTask? = null
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Lifecycle
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
init {
|
||||||
|
plugin.server.pluginManager.registerEvents( this, plugin )
|
||||||
|
startUpdateTask()
|
||||||
|
plugin.logger.info("[TablistManager] Initialisiert (Intervall: ${UPDATE_INTERVAL_TICKS * 50}ms).")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bricht den Update-Task ab, gibt alle Teams frei und setzt die Spieler
|
||||||
|
* auf das Haupt-Scoreboard zurück.
|
||||||
|
*
|
||||||
|
* In [SpeedHG.onDisable] aufrufen:
|
||||||
|
* ```kotlin
|
||||||
|
* if (::tablistManager.isInitialized) tablistManager.shutdown()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
fun shutdown()
|
||||||
|
{
|
||||||
|
updateTask?.cancel()
|
||||||
|
|
||||||
|
// Teams aus dem Scoreboard entfernen
|
||||||
|
playerTeams.values.toSet().forEach { teamName ->
|
||||||
|
scoreboard.getTeam( teamName )?.unregister()
|
||||||
|
}
|
||||||
|
playerTeams.clear()
|
||||||
|
|
||||||
|
// Spieler zurück auf das Haupt-Scoreboard (kein hängender Zustand)
|
||||||
|
val main = Bukkit.getScoreboardManager().mainScoreboard
|
||||||
|
Bukkit.getOnlinePlayers().forEach { it.scoreboard = main }
|
||||||
|
|
||||||
|
plugin.logger.info( "[TablistManager] Heruntergefahren und Teams bereinigt." )
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Öffentliche API
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualisiert den gesamten Tab-Eintrag für [player]:
|
||||||
|
* Team-Zuweisung, Prefix, Spieler-Listen-Name, Suffix und Header/Footer.
|
||||||
|
*
|
||||||
|
* Aufruf bei: Spieler-Join, Rang-Änderung, SpeedHG-Runden-Ende.
|
||||||
|
*/
|
||||||
|
fun updateTab(
|
||||||
|
player: Player
|
||||||
|
) {
|
||||||
|
assignToTeam( player )
|
||||||
|
updateHeaderFooter( player )
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Bukkit-Events
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Tick verzögert, damit Permissions (LuckPerms) und der Stats-Cache
|
||||||
|
* ([StatsManager]) sicher befüllt sind.
|
||||||
|
*/
|
||||||
|
@EventHandler(
|
||||||
|
priority = EventPriority.MONITOR
|
||||||
|
)
|
||||||
|
fun onJoin(
|
||||||
|
event: PlayerJoinEvent
|
||||||
|
) {
|
||||||
|
plugin.server.scheduler.runTask( plugin ) { ->
|
||||||
|
if ( event.player.isOnline ) updateTab( event.player )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Räumt das per-Spieler-Team beim Disconnect auf. */
|
||||||
|
@EventHandler(
|
||||||
|
priority = EventPriority.MONITOR
|
||||||
|
)
|
||||||
|
fun onQuit(
|
||||||
|
event: PlayerQuitEvent
|
||||||
|
) {
|
||||||
|
removePlayerTeam( event.player.uniqueId )
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Privat: Team-Verwaltung
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weist [player] einem eigenen Scoreboard-Team zu und setzt
|
||||||
|
* Prefix, Spieler-Listen-Name und Suffix.
|
||||||
|
*
|
||||||
|
* ### Team-Name Format
|
||||||
|
* `sHG_{gewicht}_{spielerName}`, z. B. `sHG_00_AdminBob` oder `sHG_99_Notch`.
|
||||||
|
*
|
||||||
|
* Durch den numerischen Gewicht-Präfix sortiert Minecraft Admins
|
||||||
|
* automatisch vor normalen Spielern, ohne dass wir Pakete manuell manipulieren.
|
||||||
|
*
|
||||||
|
* ### Rang-Wechsel-Erkennung
|
||||||
|
* Wenn ein Spieler die Gruppe wechselt (z. B. Beförderung zum Mod),
|
||||||
|
* weist der neue Team-Name eine andere Gewichtung auf. Das alte Team
|
||||||
|
* wird automatisch deregistriert und ein neues angelegt.
|
||||||
|
*/
|
||||||
|
private fun assignToTeam(
|
||||||
|
player: Player
|
||||||
|
) {
|
||||||
|
val weight = WEIGHT_MAP[rankProvider.getRankWeight( player )] ?: "99"
|
||||||
|
val newTeamName = "${TEAM_NAMESPACE}${weight}_${player.name}"
|
||||||
|
val oldTeamName = playerTeams[ player.uniqueId ]
|
||||||
|
|
||||||
|
// Altes Team bei Rang-Wechsel entfernen
|
||||||
|
if ( oldTeamName != null && oldTeamName != newTeamName )
|
||||||
|
{
|
||||||
|
scoreboard.getTeam( oldTeamName )?.unregister()
|
||||||
|
playerTeams.remove( player.uniqueId )
|
||||||
|
}
|
||||||
|
|
||||||
|
val team = scoreboard.getTeam( newTeamName )
|
||||||
|
?: scoreboard.registerNewTeam( newTeamName ).also { newTeam ->
|
||||||
|
// Team-Optionen einmalig beim Erstellen setzen
|
||||||
|
newTeam.setOption(
|
||||||
|
Team.Option.COLLISION_RULE,
|
||||||
|
Team.OptionStatus.NEVER
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Prefix: Server-Rang (z. B. "[Admin]") ─────────────────────────
|
||||||
|
team.prefix(rankProvider.getRankPrefix( player ))
|
||||||
|
|
||||||
|
// ── playerListName: farbiger Spielername ───────────────────────────
|
||||||
|
// Ersetzt den Standard-Anzeigenamen in der Namens-Spalte.
|
||||||
|
// Endergebnis: [PREFIX] [NAME] [SUFFIX]
|
||||||
|
val nameColor = rankProvider.getRankColor( player )
|
||||||
|
player.playerListName(mm.deserialize( "${nameColor}${player.name}<reset>" ))
|
||||||
|
|
||||||
|
// ── Suffix: SpeedHG-Rang (z. B. "[Gold II]") ──────────────────────
|
||||||
|
team.suffix(buildSpeedHGRankSuffix( player ))
|
||||||
|
|
||||||
|
// Spieler dem Team zuweisen
|
||||||
|
if (!team.hasEntry( player.name )) team.addEntry( player.name )
|
||||||
|
playerTeams[ player.uniqueId ] = newTeamName
|
||||||
|
|
||||||
|
// Scoreboard dem Spieler zuweisen (notwendig damit Teams sichtbar sind)
|
||||||
|
player.scoreboard = scoreboard
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Erstellt die Suffix-Komponente mit dem aktuellen SpeedHG-Rang. */
|
||||||
|
private fun buildSpeedHGRankSuffix(
|
||||||
|
player: Player
|
||||||
|
) = run {
|
||||||
|
val stats = plugin.statsManager.getCachedStats( player.uniqueId )
|
||||||
|
val score = stats?.scrimScore ?: 0
|
||||||
|
val games = ( stats?.wins ?: 0 ) + ( stats?.losses ?: 0 )
|
||||||
|
val rankTag = Rank.getFormattedRankTag( score, games )
|
||||||
|
|
||||||
|
mm.deserialize( " <dark_gray>[<reset>${rankTag}<dark_gray>]</dark_gray>" )
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Entfernt das Scoreboard-Team des Spielers vollständig. */
|
||||||
|
private fun removePlayerTeam(
|
||||||
|
uuid: UUID
|
||||||
|
) {
|
||||||
|
val teamName = playerTeams.remove( uuid ) ?: return
|
||||||
|
scoreboard.getTeam( teamName )?.unregister()
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Privat: Header & Footer
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Header und Footer der Tabliste für [player].
|
||||||
|
*
|
||||||
|
* Der Footer zeigt dynamische Werte (Spieleranzahl, Ping), daher wird
|
||||||
|
* diese Methode regelmäßig vom Update-Task aufgerufen.
|
||||||
|
*
|
||||||
|
* Passe die MiniMessage-Strings nach Wunsch an oder lies sie aus
|
||||||
|
* der [LanguageManager]-Konfiguration.
|
||||||
|
*/
|
||||||
|
private fun updateHeaderFooter(
|
||||||
|
player: Player
|
||||||
|
) {
|
||||||
|
val online = Bukkit.getOnlinePlayers().size
|
||||||
|
val ping = player.ping
|
||||||
|
|
||||||
|
val header = mm.deserialize(
|
||||||
|
"\n<gradient:red:gold><bold>⚔ SpeedHG ⚔</bold></gradient>\n" +
|
||||||
|
"<dark_gray>play.mcscrims.club</dark_gray>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
val footer = mm.deserialize(
|
||||||
|
"\n<gray>Online: <white><online></white> " +
|
||||||
|
"<dark_gray>⬥</dark_gray> " +
|
||||||
|
"Ping: <ping_color><ping>ms</ping_color></gray>\n",
|
||||||
|
Placeholder.unparsed( "online", online.toString() ),
|
||||||
|
Placeholder.unparsed( "ping", ping.toString() ),
|
||||||
|
// Ping-Farbe: grün < 80ms, gelb < 150ms, rot sonst
|
||||||
|
Placeholder.parsed( "ping_color", when {
|
||||||
|
ping < 80 -> "<green>"
|
||||||
|
ping < 150 -> "<yellow>"
|
||||||
|
else -> "<red>"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
player.sendPlayerListHeaderAndFooter( header, footer )
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Privat: Periodischer Update-Task
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchroner Task (Main-Thread!), der alle [UPDATE_INTERVAL_TICKS] Ticks
|
||||||
|
* für alle Online-Spieler Header/Footer und SpeedHG-Suffix aktualisiert.
|
||||||
|
*
|
||||||
|
* **Sync ist hier Pflicht**: Scoreboard-Operationen und
|
||||||
|
* `sendPlayerListHeaderAndFooter` müssen auf dem Main-Thread laufen.
|
||||||
|
*
|
||||||
|
* Nur leichtgewichtige Operationen hier — kein vollständiges
|
||||||
|
* [assignToTeam] (das wird nur bei Join/Rang-Wechsel benötigt).
|
||||||
|
*/
|
||||||
|
private fun startUpdateTask()
|
||||||
|
{
|
||||||
|
updateTask = plugin.server.scheduler.runTaskTimer( plugin, { ->
|
||||||
|
Bukkit.getOnlinePlayers().forEach { player ->
|
||||||
|
// Footer mit aktuellen Ping-Werten neu senden
|
||||||
|
updateHeaderFooter( player )
|
||||||
|
// SpeedHG-Suffix synchronisieren (falls Rang sich geändert hat)
|
||||||
|
refreshRankSuffix( player )
|
||||||
|
}
|
||||||
|
}, UPDATE_INTERVAL_TICKS, UPDATE_INTERVAL_TICKS )
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualisiert nur den Suffix des bestehenden Teams — deutlich günstiger
|
||||||
|
* als ein vollständiges [assignToTeam] bei jedem Tick.
|
||||||
|
*/
|
||||||
|
private fun refreshRankSuffix(
|
||||||
|
player: Player
|
||||||
|
) {
|
||||||
|
val teamName = playerTeams[ player.uniqueId ] ?: return
|
||||||
|
val team = scoreboard.getTeam( teamName ) ?: return
|
||||||
|
team.suffix(buildSpeedHGRankSuffix( player ))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ api-version: '1.21'
|
|||||||
depend:
|
depend:
|
||||||
- "WorldEdit"
|
- "WorldEdit"
|
||||||
- "Apollo-Bukkit"
|
- "Apollo-Bukkit"
|
||||||
|
- "Volcano"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
speedhg.bypass:
|
speedhg.bypass:
|
||||||
|
|||||||
Reference in New Issue
Block a user