Add ranking system and /ranking command
Introduce a ranking system: add Rank enum and RankingManager enhancements (isRankingEnabled toggle, getRank(), RR change handling, rank-up/down detection and player notifications with titles/sounds). Add a new /ranking admin command (toggle, status, rank) with tab completion and register it in plugin startup. Surface player rank on the scoreboard (ScoreboardManager) and add related language entries and plugin.yml permission/command metadata. Logging updated to include ranked state.
This commit is contained in:
78
src/main/kotlin/club/mcscrims/speedhg/ranking/Rank.kt
Normal file
78
src/main/kotlin/club/mcscrims/speedhg/ranking/Rank.kt
Normal file
@@ -0,0 +1,78 @@
|
||||
package club.mcscrims.speedhg.ranking
|
||||
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
|
||||
/**
|
||||
* Tier-System für SpeedHG.
|
||||
*
|
||||
* Reihenfolge der Enum-Einträge ist bewusst aufsteigend (niedrig → hoch),
|
||||
* da [ordinal] für den Rank-Up/Down-Vergleich in [RankingManager] genutzt wird.
|
||||
*
|
||||
* @param displayName Angezeigter Name (z. B. "Gold").
|
||||
* @param colorTag MiniMessage-Farbtag ohne Inhalt (z. B. "<gold>").
|
||||
* @param minScore Mindest-scrimScore für diesen Rang (inklusive). UNRANKED
|
||||
* bekommt [Int.MIN_VALUE] — er wird nie per Score gewählt,
|
||||
* sondern nur wenn der Spieler in der Placement-Phase ist.
|
||||
*/
|
||||
enum class Rank(
|
||||
val displayName: String,
|
||||
val colorTag: String,
|
||||
val minScore: Int
|
||||
) {
|
||||
UNRANKED ("Unranked", "<dark_gray>", Int.MIN_VALUE),
|
||||
BRONZE ("Bronze", "<#CD7F32>", 0 ),
|
||||
SILVER ("Silver", "<silver>", 500 ),
|
||||
GOLD ("Gold", "<gold>", 1000 ),
|
||||
PLATINUM ("Platinum", "<aqua>", 1500 ),
|
||||
DIAMOND ("Diamond", "<#B9F2FF>", 2000 ),
|
||||
SCRIM_MASTER("Scrim-Master", "<gradient:red:gold>", 2500 );
|
||||
|
||||
// ── Vorgefertigte Strings (String-Konkatenation einmalig, nicht pro Frame) ──
|
||||
|
||||
/** MiniMessage-String: "<gold>Gold<reset>". Ideal für Platzhalter in Nachrichten. */
|
||||
val tag: String = "$colorTag$displayName<reset>"
|
||||
|
||||
/** Klammer-Prefix für Chat/Scoreboard: "<gold>[Gold]<reset>". */
|
||||
val prefix: String = "$colorTag[$displayName]<reset>"
|
||||
|
||||
/**
|
||||
* Deserialisiertes Adventure-[Component] des [tag].
|
||||
* [lazy] → wird nur beim ersten Zugriff gebaut und dann gecacht.
|
||||
* Da Enum-Instanzen Singletons sind, passiert das genau einmal pro Rang.
|
||||
*/
|
||||
val component: Component by lazy { MiniMessage.miniMessage().deserialize(tag) }
|
||||
|
||||
/** Deserialisiertes Adventure-[Component] des [prefix]. */
|
||||
val prefixComponent: Component by lazy { MiniMessage.miniMessage().deserialize(prefix) }
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Gibt den **sichtbaren** Rang zurück.
|
||||
*
|
||||
* Spieler mit < [RankingManager.PLACEMENT_GAMES] abgeschlossenen Spielen
|
||||
* bekommen immer [UNRANKED], egal wie hoch ihr interner Score ist.
|
||||
*
|
||||
* @param scrimScore Aktueller Scrim-Score.
|
||||
* @param gamesPlayed Abgeschlossene Spiele (wins + losses zum Zeitpunkt der Abfrage).
|
||||
*/
|
||||
fun fromPlayer(scrimScore: Int, gamesPlayed: Int): Rank =
|
||||
if (gamesPlayed < RankingManager.PLACEMENT_GAMES) UNRANKED
|
||||
else fromScore(scrimScore)
|
||||
|
||||
/**
|
||||
* Reines Score → Tier Mapping, ignoriert die Placement-Phase.
|
||||
* Wird intern für Rank-Change-Erkennung (Pre/Post Adjustment) genutzt.
|
||||
*
|
||||
* Iteriert 6 Einträge in absteigender Score-Reihenfolge → O(1) für die Praxis.
|
||||
*/
|
||||
fun fromScore(scrimScore: Int): Rank =
|
||||
entries
|
||||
.asSequence()
|
||||
.filter { it != UNRANKED }
|
||||
.sortedByDescending { it.minScore }
|
||||
.firstOrNull { scrimScore >= it.minScore }
|
||||
?: BRONZE
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user