Update GUI-System

The GUI-System now gets its messages from the en_US.yml file for better customizability
This commit is contained in:
TDSTOS
2026-03-28 17:32:23 +01:00
parent bab703601e
commit 5b00b51193
5 changed files with 141 additions and 228 deletions

View File

@@ -12,107 +12,53 @@ import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.view.AnvilView import org.bukkit.inventory.view.AnvilView
/**
* Suchmenü auf Basis der **Paper Anvil-API** (kein NMS!).
*
* ## Wie Paper's Anvil-API funktioniert
*
* Paper stellt `player.openAnvil(location, force)` bereit. Diese Methode
* gibt eine `InventoryView` zurück. Da wir uns nicht für die physische Position
* interessieren (nur das Inventar-UI), übergeben wir `null` als Ort und
* `true` als Force-Flag (öffnet auch ohne physischen Amboss in der Nähe).
*
* ```
* Slot 0 (INPUT_LEFT) → Das "Such-Icon" mit dem Placeholder-Text
* Slot 1 (INPUT_RIGHT) → Leer (wird vom Spieler nicht benutzt)
* Slot 2 (OUTPUT) → Das Ergebnis-Item → Klick bestätigt die Suche
* ```
*
* Der Spieler schreibt in das Umbenennen-Feld des Amboss. Wir lesen den
* eingegebenen Text über `AnvilView.renameText` aus. Paper aktualisiert
* diesen Wert in Echtzeit. Beim Klick auf Slot 2 (Output) schließen wir
* den Amboss und übergeben den Text an [KitSelectorMenu.applySearch].
*
* **Warum kein `PrepareAnvilEvent` + Fake-Item?**
* Für unsere reine Text-Eingabe brauchen wir kein Live-Feedback im Output-Slot.
* Wir setzen ein statisches Bestätigungs-Item und lesen den Rename-Text beim
* Klick aus. Das ist einfacher und stabiler.
*
* @param player Der suchende Spieler.
* @param returnMenu Das [KitSelectorMenu] zu dem wir nach der Suche zurückspringen.
* @param initialText Vorausgefüllter Text (wird als Rename-Text gesetzt).
*/
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
class AnvilSearchMenu( class AnvilSearchMenu(
private val player: Player, private val player: Player,
private val returnMenu: KitSelectorMenu, private val returnMenu: KitSelectorMenu,
private val initialText: String = "" private val initialText: String = ""
) { ) {
private val plugin = SpeedHG.instance private val plugin get() = SpeedHG.instance
private val lm get() = plugin.languageManager
private val mm = MiniMessage.miniMessage() private val mm = MiniMessage.miniMessage()
/** Öffnet das Anvil-Inventar. */
fun open(player: Player) { fun open(player: Player) {
// Paper API: öffnet einen Amboss ohne physischen Block val view = player.openAnvil(null, true) ?: return
val view = player.openAnvil(null, true) ?: return
val anvilInv = view.topInventory as? AnvilInventory ?: return val anvilInv = view.topInventory as? AnvilInventory ?: return
// ── Slot 0: Such-Icon (Input) ──────────────────────────────────────
anvilInv.setItem(0, buildInputItem()) anvilInv.setItem(0, buildInputItem())
// ── Slot 2: Bestätigungs-Icon (Output) ────────────────────────────
anvilInv.setItem(2, buildConfirmItem()) anvilInv.setItem(2, buildConfirmItem())
// Kosten auf 0 setzen — Spieler soll kein XP zahlen
anvilInv.repairCost = 0 anvilInv.repairCost = 0
// Klick-Listener merken uns über die AnvilTracker-Map
AnvilSearchTracker.register(player, this) AnvilSearchTracker.register(player, this)
} }
/**
* Verarbeitet einen Klick im Anvil-Inventory.
* Wird vom [MenuListener] über [AnvilSearchTracker] weitergeleitet.
*
* @param event Das Click-Event (bereits gecancelt).
* @param view Die geöffnete [AnvilView] mit dem Eingabetext.
*/
fun onClick(event: InventoryClickEvent, view: AnvilView) { fun onClick(event: InventoryClickEvent, view: AnvilView) {
event.isCancelled = true event.isCancelled = true
// Nur Klick auf Output-Slot (Slot 2) bestätigt die Suche
if (event.rawSlot != 2) return if (event.rawSlot != 2) return
val query = view.renameText ?: "" val query = view.renameText ?: ""
// Amboss schließen und zurück zum Kit-Menü
player.closeInventory() player.closeInventory()
AnvilSearchTracker.unregister(player) AnvilSearchTracker.unregister(player)
// Suchbegriff anwenden → öffnet KitSelectorMenu neu
returnMenu.applySearch(query) returnMenu.applySearch(query)
} }
/** Cleanup wenn Spieler Amboss schließt ohne zu bestätigen. */
fun onClose() { fun onClose() {
AnvilSearchTracker.unregister(player) AnvilSearchTracker.unregister(player)
// Zurück zum Kit-Menü ohne Suchbegriff zu ändern
// Einen Tick warten, damit das aktuelle Close-Event fertig ist
plugin.server.scheduler.runTask(plugin) { -> plugin.server.scheduler.runTask(plugin) { ->
returnMenu.open(player) returnMenu.open(player)
} }
} }
// ------------------------------------------------------------------------- // ── Item-Builder ──────────────────────────────────────────────────────────
// Item-Builder
// -------------------------------------------------------------------------
private fun buildInputItem(): ItemStack { private fun buildInputItem(): ItemStack {
val rawPlaceholder = initialText.ifEmpty { lm.getRawMessage(player, "gui.anvil_search.input_placeholder") }
val item = ItemStack(Material.NAME_TAG) val item = ItemStack(Material.NAME_TAG)
item.editMeta { meta -> item.editMeta { meta ->
meta.displayName( meta.displayName(
// Der Name des Input-Items wird als Placeholder-Text im Umbenennungsfeld angezeigt mm.deserialize(rawPlaceholder)
mm.deserialize(initialText.ifEmpty { "<gray>Suchbegriff eingeben...</gray>" })
.decoration(TextDecoration.ITALIC, false) .decoration(TextDecoration.ITALIC, false)
) )
} }
@@ -123,31 +69,24 @@ class AnvilSearchMenu(
val item = ItemStack(Material.NAME_TAG) val item = ItemStack(Material.NAME_TAG)
item.editMeta { meta -> item.editMeta { meta ->
meta.displayName( meta.displayName(
mm.deserialize("<green>✔ Suche bestätigen</green>") mm.deserialize(lm.getRawMessage(player, "gui.anvil_search.confirm_name"))
.decoration(TextDecoration.ITALIC, false) .decoration(TextDecoration.ITALIC, false)
) )
val lore = listOf( meta.lore(listOf(
Component.empty(), Component.empty(),
mm.deserialize("<gray>Klicken um zu suchen</gray>") mm.deserialize(lm.getRawMessage(player, "gui.anvil_search.confirm_click"))
.decoration(TextDecoration.ITALIC, false), .decoration(TextDecoration.ITALIC, false),
Component.empty() Component.empty()
) ))
meta.lore(lore)
} }
return item return item
} }
} }
/**
* Hält die Zuordnung `Player → AnvilSearchMenu` für alle aktuell offenen
* Anvil-Suchen. Wird benötigt, weil Anvil-Inventories keinen eigenen
* [MenuHolder] haben — sie werden direkt von Bukkit verwaltet.
*
* Die Map wird im [MenuListener] abgefragt.
*/
object AnvilSearchTracker { object AnvilSearchTracker {
private val openSearches = java.util.concurrent.ConcurrentHashMap<java.util.UUID, AnvilSearchMenu>() private val openSearches =
java.util.concurrent.ConcurrentHashMap<java.util.UUID, AnvilSearchMenu>()
fun register(player: Player, menu: AnvilSearchMenu) { fun register(player: Player, menu: AnvilSearchMenu) {
openSearches[player.uniqueId] = menu openSearches[player.uniqueId] = menu

View File

@@ -1,94 +1,69 @@
package club.mcscrims.speedhg.gui.factory package club.mcscrims.speedhg.gui.factory
import club.mcscrims.speedhg.SpeedHG
import club.mcscrims.speedhg.kit.Kit import club.mcscrims.speedhg.kit.Kit
import club.mcscrims.speedhg.kit.Playstyle import club.mcscrims.speedhg.kit.Playstyle
import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.format.TextDecoration import net.kyori.adventure.text.format.TextDecoration
import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.minimessage.MiniMessage
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
import org.bukkit.Material
import org.bukkit.enchantments.Enchantment import org.bukkit.enchantments.Enchantment
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemFlag import org.bukkit.inventory.ItemFlag
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
/**
* Erstellt dynamisch die [ItemStack]s für das Kit-Auswahl-Menü.
*
* ## Warum eine eigene Factory?
*
* Die Item-Generierung (Icon, Name, Lore, Enchants) ist von [KitSelectorMenu]
* trennbar und testbar. Außerdem kann sie wiederverwendet werden (z.B. für
* Hotbar-Items oder Chat-Vorschauen).
*
* ## Lore-Aufbau
* ```
* ─────────────────────────
* <Zeile 1 aus Kit.lore>
* <Zeile 2 aus Kit.lore>
*
* Playstyle: ► Aggressive
*
* [L-Klick] Kit auswählen
* [R-Klick] Playstyle wechseln
* ─────────────────────────
* ```
*/
object KitItemFactory { object KitItemFactory {
private val plugin get() = SpeedHG.instance
private val mm = MiniMessage.miniMessage() private val mm = MiniMessage.miniMessage()
/** // ── Kit-Item ──────────────────────────────────────────────────────────────
* Baut das vollständige Kit-Item inklusive dynamischer Lore.
*
* @param kit Das Kit, das dargestellt werden soll.
* @param currentStyle Der aktuell gewählte [Playstyle] des Spielers.
* @param isSelected Ob dieses Kit gerade das aktiv gewählte ist.
*/
fun buildKitItem( fun buildKitItem(
kit: Kit, kit: Kit,
currentStyle: Playstyle, currentStyle: Playstyle,
isSelected: Boolean isSelected: Boolean,
player: Player
): ItemStack { ): ItemStack {
val lm = plugin.languageManager
val item = ItemStack(kit.icon) val item = ItemStack(kit.icon)
item.editMeta { meta -> item.editMeta { meta ->
// ── Name ──────────────────────────────────────────────────────────
meta.displayName( meta.displayName(
kit.displayName kit.displayName.decoration(TextDecoration.ITALIC, false)
.decoration(TextDecoration.ITALIC, false)
) )
// ── Lore ──────────────────────────────────────────────────────────
val lore = mutableListOf<Component>() val lore = mutableListOf<Component>()
// Trennlinie
lore += separator() lore += separator()
// Kit-eigene Lore-Zeilen
kit.lore.forEach { line -> kit.lore.forEach { line ->
lore += mm.deserialize(line) lore += mm.deserialize(line).decoration(TextDecoration.ITALIC, false)
.decoration(TextDecoration.ITALIC, false)
} }
lore += Component.empty() lore += Component.empty()
// Playstyle-Anzeige
lore += mm.deserialize( lore += mm.deserialize(
"<gray>Playstyle: <white>► ${currentStyle.displayName}" lm.getRawMessage(player, "gui.kit_selector.kit_item.playstyle"),
Placeholder.parsed("playstyle", currentStyle.displayName)
).decoration(TextDecoration.ITALIC, false) ).decoration(TextDecoration.ITALIC, false)
lore += Component.empty() lore += Component.empty()
// Interaktions-Hints lore += mm.deserialize(
lore += mm.deserialize("<yellow>[L-Click]</yellow> <gray>Select Kit</gray>") lm.getRawMessage(player, "gui.kit_selector.kit_item.left_click")
.decoration(TextDecoration.ITALIC, false) ).decoration(TextDecoration.ITALIC, false)
lore += mm.deserialize("<gold>[R-Click]</gold> <gray>Change playstyle</gray>")
.decoration(TextDecoration.ITALIC, false) lore += mm.deserialize(
lm.getRawMessage(player, "gui.kit_selector.kit_item.right_click")
).decoration(TextDecoration.ITALIC, false)
lore += separator() lore += separator()
meta.lore(lore) meta.lore(lore)
// ── Ausgewählt-Effekt ──────────────────────────────────────────────
if (isSelected) { if (isSelected) {
meta.addEnchant(Enchantment.UNBREAKING, 1, true) meta.addEnchant(Enchantment.UNBREAKING, 1, true)
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS) meta.addItemFlags(ItemFlag.HIDE_ENCHANTS)
@@ -103,66 +78,77 @@ object KitItemFactory {
return item return item
} }
// ── Dekorations-Items ───────────────────────────────────────────────────── // ── Dekorations- und Navigations-Items ───────────────────────────────────
fun buildFillerItem(): ItemStack { fun buildFillerItem(): ItemStack {
val item = ItemStack(org.bukkit.Material.GRAY_STAINED_GLASS_PANE) val item = ItemStack(Material.GRAY_STAINED_GLASS_PANE)
item.editMeta { meta -> item.editMeta { meta ->
meta.displayName(Component.text(" ").decoration(TextDecoration.ITALIC, false)) meta.displayName(
Component.text(" ").decoration(TextDecoration.ITALIC, false)
)
} }
return item return item
} }
fun buildPrevPageItem(): ItemStack = buildNavItem( fun buildPrevPageItem(player: Player): ItemStack = buildNavItem(
org.bukkit.Material.ARROW, Material.ARROW,
"<red>◄ Previous" plugin.languageManager.getRawMessage(player, "gui.kit_selector.prev_page")
) )
fun buildNextPageItem(): ItemStack = buildNavItem( fun buildNextPageItem(player: Player): ItemStack = buildNavItem(
org.bukkit.Material.ARROW, Material.ARROW,
"<green>Next ►" plugin.languageManager.getRawMessage(player, "gui.kit_selector.next_page")
) )
fun buildSearchItem(currentQuery: String): ItemStack { fun buildSearchItem(currentQuery: String, player: Player): ItemStack {
val item = ItemStack(org.bukkit.Material.NAME_TAG) val lm = plugin.languageManager
val item = ItemStack(Material.NAME_TAG)
item.editMeta { meta -> item.editMeta { meta ->
meta.displayName( meta.displayName(
mm.deserialize("<yellow>🔍 Search") mm.deserialize(lm.getRawMessage(player, "gui.kit_selector.search.name"))
.decoration(TextDecoration.ITALIC, false) .decoration(TextDecoration.ITALIC, false)
) )
val lore = mutableListOf<Component>() val lore = mutableListOf<Component>()
lore += separator() lore += separator()
if (currentQuery.isNotEmpty()) { if (currentQuery.isNotEmpty()) {
lore += mm.deserialize("<gray>Current: <white>\"$currentQuery\"") lore += mm.deserialize(
.decoration(TextDecoration.ITALIC, false) lm.getRawMessage(player, "gui.kit_selector.search.current"),
Placeholder.parsed("query", currentQuery)
).decoration(TextDecoration.ITALIC, false)
lore += Component.empty() lore += Component.empty()
} }
lore += mm.deserialize("<gray>Click to search</gray>")
.decoration(TextDecoration.ITALIC, false) lore += mm.deserialize(
lm.getRawMessage(player, "gui.kit_selector.search.click")
).decoration(TextDecoration.ITALIC, false)
lore += separator() lore += separator()
meta.lore(lore) meta.lore(lore)
} }
return item return item
} }
fun buildClearSearchItem(): ItemStack = buildNavItem( fun buildClearSearchItem(player: Player): ItemStack = buildNavItem(
org.bukkit.Material.BARRIER, Material.BARRIER,
"<red>✕ Reset search" plugin.languageManager.getRawMessage(player, "gui.kit_selector.clear_search")
) )
fun buildCloseItem(): ItemStack = buildNavItem( fun buildCloseItem(player: Player): ItemStack = buildNavItem(
org.bukkit.Material.DARK_OAK_DOOR, Material.DARK_OAK_DOOR,
"<red>✕ Close" plugin.languageManager.getRawMessage(player, "gui.kit_selector.close")
) )
// ── Interne Hilfsmittel ─────────────────────────────────────────────────── // ── Interne Hilfsmittel ───────────────────────────────────────────────────
private fun buildNavItem(material: org.bukkit.Material, title: String): ItemStack { private fun buildNavItem(material: Material, rawTitle: String): ItemStack {
val item = ItemStack(material) val item = ItemStack(material)
item.editMeta { meta -> item.editMeta { meta ->
meta.displayName( meta.displayName(
mm.deserialize(title) mm.deserialize(rawTitle).decoration(TextDecoration.ITALIC, false)
.decoration(TextDecoration.ITALIC, false)
) )
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ADDITIONAL_TOOLTIP) meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ADDITIONAL_TOOLTIP)
} }

View File

@@ -5,7 +5,7 @@ import club.mcscrims.speedhg.gui.anvil.AnvilSearchMenu
import club.mcscrims.speedhg.gui.factory.KitItemFactory import club.mcscrims.speedhg.gui.factory.KitItemFactory
import club.mcscrims.speedhg.kit.Kit import club.mcscrims.speedhg.kit.Kit
import club.mcscrims.speedhg.kit.Playstyle import club.mcscrims.speedhg.kit.Playstyle
import net.kyori.adventure.text.minimessage.MiniMessage import club.mcscrims.speedhg.util.trans
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.event.inventory.ClickType import org.bukkit.event.inventory.ClickType
import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryClickEvent
@@ -32,23 +32,19 @@ import org.bukkit.inventory.Inventory
class KitSelectorMenu( class KitSelectorMenu(
private val player: Player, private val player: Player,
private var searchQuery: String = "" private var searchQuery: String = ""
) : Menu(rows = 6, title = "<gradient:red:gold><bold>Kit-Auswahl</bold></gradient>") { ) : Menu(
rows = 6,
title = player.trans( "gui.kit_selector.title" )
) {
private val plugin = SpeedHG.instance private val plugin get() = SpeedHG.instance
private val mm = MiniMessage.miniMessage() private val lm get() = plugin.languageManager
// Pagination // Pagination
private val kitSlots = 45 // Slots 044 für Kits private val kitSlots = 45 // Slots 044 für Kits
private var currentPage = 0 private var currentPage = 0
// ── Slot-Konstanten ──────────────────────────────────────────────────────── // ── Slot-Konstanten ────────────────────────────────────────────────────────
private val slotPrevPage = 45
private val slotSearch = 48
private val slotClearSearch = 50
private val slotClose = 53
private val slotNextPage = 53 // wird durch slotClose ersetzt wenn letzte Seite
// ── Navigation-Slots ──────────────────────────────────────────────────────
private val navSlotPrev = 45 private val navSlotPrev = 45
private val navSlotSearch = 48 private val navSlotSearch = 48
private val navSlotClear = 50 private val navSlotClear = 50
@@ -59,7 +55,7 @@ class KitSelectorMenu(
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
override fun build(): Inventory { override fun build(): Inventory {
val inv = createInventory(mm.deserialize(title)) val inv = createInventory(title) // title ist jetzt bereits ein Component
populate(inv) populate(inv)
return inv return inv
} }
@@ -67,70 +63,65 @@ class KitSelectorMenu(
private fun populate(inv: Inventory) { private fun populate(inv: Inventory) {
inv.clear() inv.clear()
// ── Filler ──────────────────────────────────────────────────────────── // Filler-Reihe
val filler = KitItemFactory.buildFillerItem() val filler = KitItemFactory.buildFillerItem()
(45 until 54).forEach { inv.setItem(it, filler) } (45 until 54).forEach { inv.setItem(it, filler) }
// ── Kits ────────────────────────────────────────────────────────────── // Kits
val filteredKits = getFilteredKits() val filteredKits = getFilteredKits()
val totalPages = ((filteredKits.size - 1) / kitSlots + 1).coerceAtLeast(1) val totalPages = ((filteredKits.size - 1) / kitSlots + 1).coerceAtLeast(1)
// Seiten-Clamp: wenn Suche Kits reduziert, nicht auf nicht-existenter Seite bleiben
currentPage = currentPage.coerceIn(0, totalPages - 1) currentPage = currentPage.coerceIn(0, totalPages - 1)
val pageStart = currentPage * kitSlots val pageStart = currentPage * kitSlots
val pageKits = filteredKits.subList( val pageKits = filteredKits.subList(
pageStart.coerceAtMost(filteredKits.size), pageStart.coerceAtMost(filteredKits.size),
(pageStart + kitSlots).coerceAtMost(filteredKits.size) (pageStart + kitSlots).coerceAtMost(filteredKits.size)
) )
pageKits.forEachIndexed { slotIndex, kit -> pageKits.forEachIndexed { slotIndex, kit ->
val currentStyle = plugin.kitManager.getSelectedPlaystyle(player) val currentStyle = plugin.kitManager.getSelectedPlaystyle(player)
val selectedKit = plugin.kitManager.getSelectedKit(player) val selectedKit = plugin.kitManager.getSelectedKit(player)
inv.setItem(slotIndex, KitItemFactory.buildKitItem(kit, currentStyle, selectedKit?.id == kit.id)) inv.setItem(
slotIndex,
KitItemFactory.buildKitItem(kit, currentStyle, selectedKit?.id == kit.id, player)
)
} }
// ── Navigation ──────────────────────────────────────────────────────── // Navigation
if (currentPage > 0) if (currentPage > 0)
inv.setItem(navSlotPrev, KitItemFactory.buildPrevPageItem()) inv.setItem(navSlotPrev, KitItemFactory.buildPrevPageItem(player))
if (currentPage < totalPages - 1) if (currentPage < totalPages - 1)
inv.setItem(navSlotNext, KitItemFactory.buildNextPageItem()) inv.setItem(navSlotNext, KitItemFactory.buildNextPageItem(player))
inv.setItem(navSlotSearch, KitItemFactory.buildSearchItem(searchQuery)) inv.setItem(navSlotSearch, KitItemFactory.buildSearchItem(searchQuery, player))
if (searchQuery.isNotEmpty()) if (searchQuery.isNotEmpty())
inv.setItem(navSlotClear, KitItemFactory.buildClearSearchItem()) inv.setItem(navSlotClear, KitItemFactory.buildClearSearchItem(player))
} }
// ------------------------------------------------------------------------- // ── Klick-Handling ────────────────────────────────────────────────────────
// Klick-Handling
// -------------------------------------------------------------------------
override fun onClick(event: InventoryClickEvent, player: Player) { override fun onClick(event: InventoryClickEvent, player: Player) {
val slot = event.rawSlot val slot = event.rawSlot
if (slot !in 0..<size) return if (slot !in 0..<size) return
when (slot) { when (slot) {
navSlotPrev -> handlePrevPage() navSlotPrev -> handlePrevPage()
navSlotNext -> handleNextPage() navSlotNext -> handleNextPage()
navSlotSearch -> handleSearchClick() navSlotSearch -> handleSearchClick()
navSlotClear -> handleClearSearch() navSlotClear -> handleClearSearch()
in 0 until kitSlots -> handleKitClick(slot, event.click) in 0 until kitSlots -> handleKitClick(slot, event.click)
} }
} }
// ------------------------------------------------------------------------- // ── Kit-Interaktionen ─────────────────────────────────────────────────────
// Kit-Interaktionen
// -------------------------------------------------------------------------
private fun handleKitClick(slot: Int, clickType: ClickType) { private fun handleKitClick(slot: Int, clickType: ClickType) {
val kit = getKitAtSlot(slot) ?: return val kit = getKitAtSlot(slot) ?: return
when { when {
// Linksklick → Kit auswählen
clickType.isLeftClick -> selectKit(kit) clickType.isLeftClick -> selectKit(kit)
// Rechtsklick → Playstyle toggeln (und Menü refreshen)
clickType.isRightClick -> togglePlaystyle(kit) clickType.isRightClick -> togglePlaystyle(kit)
} }
} }
@@ -138,27 +129,27 @@ class KitSelectorMenu(
private fun selectKit(kit: Kit) { private fun selectKit(kit: Kit) {
plugin.kitManager.selectKit(player, kit) plugin.kitManager.selectKit(player, kit)
player.sendActionBar( player.sendActionBar(
MiniMessage.miniMessage().deserialize( lm.getComponent(
"<green>Kit <white>${kit.displayName}</white> gewählt!</green>" player, "gui.kit_selector.kit_selected",
mapOf(), mapOf("kit" to kit.displayName)
) )
) )
// Menü aktualisieren um Auswahl-Glanz zu zeigen
refresh() refresh()
} }
private fun togglePlaystyle(kit: Kit) { private fun togglePlaystyle(kit: Kit) {
val current = plugin.kitManager.getSelectedPlaystyle(player) val current = plugin.kitManager.getSelectedPlaystyle(player)
val next = when (current) { val next = when (current) {
Playstyle.AGGRESSIVE -> Playstyle.DEFENSIVE Playstyle.AGGRESSIVE -> Playstyle.DEFENSIVE
Playstyle.DEFENSIVE -> Playstyle.AGGRESSIVE Playstyle.DEFENSIVE -> Playstyle.AGGRESSIVE
} }
plugin.kitManager.selectPlaystyle(player, next) plugin.kitManager.selectPlaystyle(player, next)
// Wenn dieses Kit gerade ausgewählt ist, Playstyle-Änderung übernehmen
if (plugin.kitManager.getSelectedKit(player)?.id == kit.id) { if (plugin.kitManager.getSelectedKit(player)?.id == kit.id) {
player.sendActionBar( player.sendActionBar(
MiniMessage.miniMessage().deserialize( lm.getComponent(
"<gold>Playstyle: <white>► ${next.displayName}</white>" player, "gui.kit_selector.playstyle_changed",
mapOf("playstyle" to next.displayName)
) )
) )
} }
@@ -166,32 +157,20 @@ class KitSelectorMenu(
refresh() refresh()
} }
// ------------------------------------------------------------------------- // ── Navigation ────────────────────────────────────────────────────────────
// Navigation
// -------------------------------------------------------------------------
private fun handlePrevPage() { private fun handlePrevPage() {
if (currentPage > 0) { if (currentPage > 0) { currentPage--; refresh() }
currentPage--
refresh()
}
} }
private fun handleNextPage() { private fun handleNextPage() {
val totalPages = ((getFilteredKits().size - 1) / kitSlots + 1).coerceAtLeast(1) val totalPages = ((getFilteredKits().size - 1) / kitSlots + 1).coerceAtLeast(1)
if (currentPage < totalPages - 1) { if (currentPage < totalPages - 1) { currentPage++; refresh() }
currentPage++
refresh()
}
} }
private fun handleSearchClick() { private fun handleSearchClick() {
// Öffne das Anvil-Suchmenü; schließt dieses Menü temporär AnvilSearchMenu(player = player, returnMenu = this, initialText = searchQuery)
AnvilSearchMenu( .open(player)
player = player,
returnMenu = this,
initialText = searchQuery
).open(player)
} }
private fun handleClearSearch() { private fun handleClearSearch() {
@@ -200,23 +179,14 @@ class KitSelectorMenu(
refresh() refresh()
} }
// ------------------------------------------------------------------------- // ── Hilfsmittel ───────────────────────────────────────────────────────────
// Hilfsmittel
// -------------------------------------------------------------------------
/** Aktualisiert das geöffnete Inventory in-place ohne es neu zu öffnen. */ fun refresh() { populate(inventory) }
fun refresh() {
populate(inventory)
}
/**
* Aktualisiert den Suchbegriff und baut das Menü neu auf.
* Wird vom [AnvilSearchMenu] aufgerufen nachdem der Spieler einen Begriff eingegeben hat.
*/
fun applySearch(query: String) { fun applySearch(query: String) {
searchQuery = query.trim() searchQuery = query.trim()
currentPage = 0 currentPage = 0
open(player) // neu öffnen (Anvil hat das vorherige Inventory geschlossen) open(player)
} }
private fun getFilteredKits(): List<Kit> { private fun getFilteredKits(): List<Kit> {
@@ -225,19 +195,14 @@ class KitSelectorMenu(
val q = searchQuery.lowercase() val q = searchQuery.lowercase()
return allKits.filter { kit -> return allKits.filter { kit ->
// Suche im Plain-Text des Adventure-Components
kit.id.lowercase().contains(q) || kit.id.lowercase().contains(q) ||
net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer
.plainText() .plainText().serialize(kit.displayName).lowercase().contains(q)
.serialize(kit.displayName)
.lowercase()
.contains(q)
} }
} }
private fun getKitAtSlot(slot: Int): Kit? { private fun getKitAtSlot(slot: Int): Kit? {
val filteredKits = getFilteredKits()
val absoluteIndex = currentPage * kitSlots + slot val absoluteIndex = currentPage * kitSlots + slot
return filteredKits.getOrNull(absoluteIndex) return getFilteredKits().getOrNull(absoluteIndex)
} }
} }

View File

@@ -1,5 +1,6 @@
package club.mcscrims.speedhg.gui.menu package club.mcscrims.speedhg.gui.menu
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryClickEvent
@@ -25,7 +26,7 @@ import org.bukkit.inventory.Inventory
*/ */
abstract class Menu( abstract class Menu(
protected val rows: Int, protected val rows: Int,
protected val title: String protected val title: Component
) { ) {
protected val size: Int = rows * 9 protected val size: Int = rows * 9
internal lateinit var inventory: Inventory internal lateinit var inventory: Inventory
@@ -56,7 +57,7 @@ abstract class Menu(
* Erstellt ein Inventory mit dem korrekten [MenuHolder]. * Erstellt ein Inventory mit dem korrekten [MenuHolder].
* Immer statt `Bukkit.createInventory(null, ...)` verwenden. * Immer statt `Bukkit.createInventory(null, ...)` verwenden.
*/ */
protected fun createInventory(parsedTitle: net.kyori.adventure.text.Component): Inventory { protected fun createInventory(parsedTitle: Component): Inventory {
val holder = MenuHolder(this) val holder = MenuHolder(this)
val inv = Bukkit.createInventory(holder, size, parsedTitle) val inv = Bukkit.createInventory(holder, size, parsedTitle)
holder.bind(inv) holder.bind(inv)

View File

@@ -95,6 +95,28 @@ scoreboard:
- "" - ""
- "<yellow>play.mcscrims.club" - "<yellow>play.mcscrims.club"
gui:
kit_selector:
title: '<gradient:red:gold><bold>Kit Selection</bold></gradient>'
kit_selected: '<green>Kit <kit> selected!</green>'
playstyle_changed: '<gold>Playstyle: <white>► <playstyle></white>'
prev_page: '<red>◄ Previous'
next_page: '<green>Next ►'
clear_search: '<red>✕ Reset Search'
close: '<red>✕ Close'
kit_item:
playstyle: '<gray>Playstyle: <white>► <playstyle>'
left_click: '<yellow>[L-Click]</yellow> <gray>Select Kit</gray>'
right_click: '<gold>[R-Click]</gold> <gray>Change Playstyle</gray>'
search:
name: '<yellow>🔍 Search'
current: '<gray>Current: <white>"<query>"'
click: '<gray>Click to search</gray>'
anvil_search:
input_placeholder: '<gray>Enter search term...</gray>'
confirm_name: '<green>✔ Confirm Search</green>'
confirm_click: '<gray>Click to confirm</gray>'
kits: kits:
backup: backup:
name: '<gradient:gold:#ff841f><bold>Backup</bold></gradient>' name: '<gradient:gold:#ff841f><bold>Backup</bold></gradient>'