Update GUI-System
The GUI-System now gets its messages from the en_US.yml file for better customizability
This commit is contained in:
@@ -12,107 +12,53 @@ import org.bukkit.inventory.AnvilInventory
|
||||
import org.bukkit.inventory.ItemStack
|
||||
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")
|
||||
class AnvilSearchMenu(
|
||||
private val player: Player,
|
||||
private val returnMenu: KitSelectorMenu,
|
||||
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()
|
||||
|
||||
/** Öffnet das Anvil-Inventar. */
|
||||
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
|
||||
|
||||
// ── Slot 0: Such-Icon (Input) ──────────────────────────────────────
|
||||
anvilInv.setItem(0, buildInputItem())
|
||||
|
||||
// ── Slot 2: Bestätigungs-Icon (Output) ────────────────────────────
|
||||
anvilInv.setItem(2, buildConfirmItem())
|
||||
|
||||
// Kosten auf 0 setzen — Spieler soll kein XP zahlen
|
||||
anvilInv.repairCost = 0
|
||||
|
||||
// Klick-Listener merken uns über die AnvilTracker-Map
|
||||
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) {
|
||||
event.isCancelled = true
|
||||
|
||||
// Nur Klick auf Output-Slot (Slot 2) bestätigt die Suche
|
||||
if (event.rawSlot != 2) return
|
||||
|
||||
val query = view.renameText ?: ""
|
||||
|
||||
// Amboss schließen und zurück zum Kit-Menü
|
||||
player.closeInventory()
|
||||
AnvilSearchTracker.unregister(player)
|
||||
|
||||
// Suchbegriff anwenden → öffnet KitSelectorMenu neu
|
||||
returnMenu.applySearch(query)
|
||||
}
|
||||
|
||||
/** Cleanup wenn Spieler Amboss schließt ohne zu bestätigen. */
|
||||
fun onClose() {
|
||||
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) { ->
|
||||
returnMenu.open(player)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Item-Builder
|
||||
// -------------------------------------------------------------------------
|
||||
// ── Item-Builder ──────────────────────────────────────────────────────────
|
||||
|
||||
private fun buildInputItem(): ItemStack {
|
||||
val rawPlaceholder = initialText.ifEmpty { lm.getRawMessage(player, "gui.anvil_search.input_placeholder") }
|
||||
|
||||
val item = ItemStack(Material.NAME_TAG)
|
||||
item.editMeta { meta ->
|
||||
meta.displayName(
|
||||
// Der Name des Input-Items wird als Placeholder-Text im Umbenennungsfeld angezeigt
|
||||
mm.deserialize(initialText.ifEmpty { "<gray>Suchbegriff eingeben...</gray>" })
|
||||
mm.deserialize(rawPlaceholder)
|
||||
.decoration(TextDecoration.ITALIC, false)
|
||||
)
|
||||
}
|
||||
@@ -123,31 +69,24 @@ class AnvilSearchMenu(
|
||||
val item = ItemStack(Material.NAME_TAG)
|
||||
item.editMeta { meta ->
|
||||
meta.displayName(
|
||||
mm.deserialize("<green>✔ Suche bestätigen</green>")
|
||||
mm.deserialize(lm.getRawMessage(player, "gui.anvil_search.confirm_name"))
|
||||
.decoration(TextDecoration.ITALIC, false)
|
||||
)
|
||||
val lore = listOf(
|
||||
meta.lore(listOf(
|
||||
Component.empty(),
|
||||
mm.deserialize("<gray>Klicken um zu suchen</gray>")
|
||||
mm.deserialize(lm.getRawMessage(player, "gui.anvil_search.confirm_click"))
|
||||
.decoration(TextDecoration.ITALIC, false),
|
||||
Component.empty()
|
||||
)
|
||||
meta.lore(lore)
|
||||
))
|
||||
}
|
||||
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 {
|
||||
|
||||
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) {
|
||||
openSearches[player.uniqueId] = menu
|
||||
|
||||
@@ -1,94 +1,69 @@
|
||||
package club.mcscrims.speedhg.gui.factory
|
||||
|
||||
import club.mcscrims.speedhg.SpeedHG
|
||||
import club.mcscrims.speedhg.kit.Kit
|
||||
import club.mcscrims.speedhg.kit.Playstyle
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
import net.kyori.adventure.text.format.TextDecoration
|
||||
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.entity.Player
|
||||
import org.bukkit.inventory.ItemFlag
|
||||
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 {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
private val mm = MiniMessage.miniMessage()
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// ── Kit-Item ──────────────────────────────────────────────────────────────
|
||||
|
||||
fun buildKitItem(
|
||||
kit: Kit,
|
||||
currentStyle: Playstyle,
|
||||
isSelected: Boolean
|
||||
isSelected: Boolean,
|
||||
player: Player
|
||||
): ItemStack {
|
||||
val lm = plugin.languageManager
|
||||
val item = ItemStack(kit.icon)
|
||||
|
||||
item.editMeta { meta ->
|
||||
// ── Name ──────────────────────────────────────────────────────────
|
||||
meta.displayName(
|
||||
kit.displayName
|
||||
.decoration(TextDecoration.ITALIC, false)
|
||||
kit.displayName.decoration(TextDecoration.ITALIC, false)
|
||||
)
|
||||
|
||||
// ── Lore ──────────────────────────────────────────────────────────
|
||||
val lore = mutableListOf<Component>()
|
||||
|
||||
// Trennlinie
|
||||
lore += separator()
|
||||
|
||||
// Kit-eigene Lore-Zeilen
|
||||
kit.lore.forEach { line ->
|
||||
lore += mm.deserialize(line)
|
||||
.decoration(TextDecoration.ITALIC, false)
|
||||
lore += mm.deserialize(line).decoration(TextDecoration.ITALIC, false)
|
||||
}
|
||||
|
||||
lore += Component.empty()
|
||||
|
||||
// Playstyle-Anzeige
|
||||
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)
|
||||
|
||||
lore += Component.empty()
|
||||
|
||||
// Interaktions-Hints
|
||||
lore += mm.deserialize("<yellow>[L-Click]</yellow> <gray>Select Kit</gray>")
|
||||
.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.left_click")
|
||||
).decoration(TextDecoration.ITALIC, false)
|
||||
|
||||
lore += mm.deserialize(
|
||||
lm.getRawMessage(player, "gui.kit_selector.kit_item.right_click")
|
||||
).decoration(TextDecoration.ITALIC, false)
|
||||
|
||||
lore += separator()
|
||||
|
||||
meta.lore(lore)
|
||||
|
||||
// ── Ausgewählt-Effekt ──────────────────────────────────────────────
|
||||
if (isSelected) {
|
||||
meta.addEnchant(Enchantment.UNBREAKING, 1, true)
|
||||
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS)
|
||||
@@ -103,66 +78,77 @@ object KitItemFactory {
|
||||
return item
|
||||
}
|
||||
|
||||
// ── Dekorations-Items ─────────────────────────────────────────────────────
|
||||
// ── Dekorations- und Navigations-Items ───────────────────────────────────
|
||||
|
||||
fun buildFillerItem(): ItemStack {
|
||||
val item = ItemStack(org.bukkit.Material.GRAY_STAINED_GLASS_PANE)
|
||||
val item = ItemStack(Material.GRAY_STAINED_GLASS_PANE)
|
||||
item.editMeta { meta ->
|
||||
meta.displayName(Component.text(" ").decoration(TextDecoration.ITALIC, false))
|
||||
meta.displayName(
|
||||
Component.text(" ").decoration(TextDecoration.ITALIC, false)
|
||||
)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
fun buildPrevPageItem(): ItemStack = buildNavItem(
|
||||
org.bukkit.Material.ARROW,
|
||||
"<red>◄ Previous"
|
||||
fun buildPrevPageItem(player: Player): ItemStack = buildNavItem(
|
||||
Material.ARROW,
|
||||
plugin.languageManager.getRawMessage(player, "gui.kit_selector.prev_page")
|
||||
)
|
||||
|
||||
fun buildNextPageItem(): ItemStack = buildNavItem(
|
||||
org.bukkit.Material.ARROW,
|
||||
"<green>Next ►"
|
||||
fun buildNextPageItem(player: Player): ItemStack = buildNavItem(
|
||||
Material.ARROW,
|
||||
plugin.languageManager.getRawMessage(player, "gui.kit_selector.next_page")
|
||||
)
|
||||
|
||||
fun buildSearchItem(currentQuery: String): ItemStack {
|
||||
val item = ItemStack(org.bukkit.Material.NAME_TAG)
|
||||
fun buildSearchItem(currentQuery: String, player: Player): ItemStack {
|
||||
val lm = plugin.languageManager
|
||||
val item = ItemStack(Material.NAME_TAG)
|
||||
|
||||
item.editMeta { meta ->
|
||||
meta.displayName(
|
||||
mm.deserialize("<yellow>🔍 Search")
|
||||
mm.deserialize(lm.getRawMessage(player, "gui.kit_selector.search.name"))
|
||||
.decoration(TextDecoration.ITALIC, false)
|
||||
)
|
||||
|
||||
val lore = mutableListOf<Component>()
|
||||
lore += separator()
|
||||
|
||||
if (currentQuery.isNotEmpty()) {
|
||||
lore += mm.deserialize("<gray>Current: <white>\"$currentQuery\"")
|
||||
.decoration(TextDecoration.ITALIC, false)
|
||||
lore += mm.deserialize(
|
||||
lm.getRawMessage(player, "gui.kit_selector.search.current"),
|
||||
Placeholder.parsed("query", currentQuery)
|
||||
).decoration(TextDecoration.ITALIC, false)
|
||||
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()
|
||||
meta.lore(lore)
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
fun buildClearSearchItem(): ItemStack = buildNavItem(
|
||||
org.bukkit.Material.BARRIER,
|
||||
"<red>✕ Reset search"
|
||||
fun buildClearSearchItem(player: Player): ItemStack = buildNavItem(
|
||||
Material.BARRIER,
|
||||
plugin.languageManager.getRawMessage(player, "gui.kit_selector.clear_search")
|
||||
)
|
||||
|
||||
fun buildCloseItem(): ItemStack = buildNavItem(
|
||||
org.bukkit.Material.DARK_OAK_DOOR,
|
||||
"<red>✕ Close"
|
||||
fun buildCloseItem(player: Player): ItemStack = buildNavItem(
|
||||
Material.DARK_OAK_DOOR,
|
||||
plugin.languageManager.getRawMessage(player, "gui.kit_selector.close")
|
||||
)
|
||||
|
||||
// ── Interne Hilfsmittel ───────────────────────────────────────────────────
|
||||
|
||||
private fun buildNavItem(material: org.bukkit.Material, title: String): ItemStack {
|
||||
private fun buildNavItem(material: Material, rawTitle: String): ItemStack {
|
||||
val item = ItemStack(material)
|
||||
item.editMeta { meta ->
|
||||
meta.displayName(
|
||||
mm.deserialize(title)
|
||||
.decoration(TextDecoration.ITALIC, false)
|
||||
mm.deserialize(rawTitle).decoration(TextDecoration.ITALIC, false)
|
||||
)
|
||||
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ADDITIONAL_TOOLTIP)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import club.mcscrims.speedhg.gui.anvil.AnvilSearchMenu
|
||||
import club.mcscrims.speedhg.gui.factory.KitItemFactory
|
||||
import club.mcscrims.speedhg.kit.Kit
|
||||
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.event.inventory.ClickType
|
||||
import org.bukkit.event.inventory.InventoryClickEvent
|
||||
@@ -32,23 +32,19 @@ import org.bukkit.inventory.Inventory
|
||||
class KitSelectorMenu(
|
||||
private val player: Player,
|
||||
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 mm = MiniMessage.miniMessage()
|
||||
private val plugin get() = SpeedHG.instance
|
||||
private val lm get() = plugin.languageManager
|
||||
|
||||
// Pagination
|
||||
private val kitSlots = 45 // Slots 0–44 für Kits
|
||||
private var currentPage = 0
|
||||
|
||||
// ── 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 navSlotSearch = 48
|
||||
private val navSlotClear = 50
|
||||
@@ -59,7 +55,7 @@ class KitSelectorMenu(
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
override fun build(): Inventory {
|
||||
val inv = createInventory(mm.deserialize(title))
|
||||
val inv = createInventory(title) // title ist jetzt bereits ein Component
|
||||
populate(inv)
|
||||
return inv
|
||||
}
|
||||
@@ -67,70 +63,65 @@ class KitSelectorMenu(
|
||||
private fun populate(inv: Inventory) {
|
||||
inv.clear()
|
||||
|
||||
// ── Filler ────────────────────────────────────────────────────────────
|
||||
// Filler-Reihe
|
||||
val filler = KitItemFactory.buildFillerItem()
|
||||
(45 until 54).forEach { inv.setItem(it, filler) }
|
||||
|
||||
// ── Kits ──────────────────────────────────────────────────────────────
|
||||
// Kits
|
||||
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)
|
||||
|
||||
val pageStart = currentPage * kitSlots
|
||||
val pageKits = filteredKits.subList(
|
||||
val pageKits = filteredKits.subList(
|
||||
pageStart.coerceAtMost(filteredKits.size),
|
||||
(pageStart + kitSlots).coerceAtMost(filteredKits.size)
|
||||
)
|
||||
|
||||
pageKits.forEachIndexed { slotIndex, kit ->
|
||||
val currentStyle = plugin.kitManager.getSelectedPlaystyle(player)
|
||||
val selectedKit = plugin.kitManager.getSelectedKit(player)
|
||||
inv.setItem(slotIndex, KitItemFactory.buildKitItem(kit, currentStyle, selectedKit?.id == kit.id))
|
||||
val selectedKit = plugin.kitManager.getSelectedKit(player)
|
||||
inv.setItem(
|
||||
slotIndex,
|
||||
KitItemFactory.buildKitItem(kit, currentStyle, selectedKit?.id == kit.id, player)
|
||||
)
|
||||
}
|
||||
|
||||
// ── Navigation ────────────────────────────────────────────────────────
|
||||
// Navigation
|
||||
if (currentPage > 0)
|
||||
inv.setItem(navSlotPrev, KitItemFactory.buildPrevPageItem())
|
||||
inv.setItem(navSlotPrev, KitItemFactory.buildPrevPageItem(player))
|
||||
|
||||
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())
|
||||
inv.setItem(navSlotClear, KitItemFactory.buildClearSearchItem())
|
||||
inv.setItem(navSlotClear, KitItemFactory.buildClearSearchItem(player))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Klick-Handling
|
||||
// -------------------------------------------------------------------------
|
||||
// ── Klick-Handling ────────────────────────────────────────────────────────
|
||||
|
||||
override fun onClick(event: InventoryClickEvent, player: Player) {
|
||||
val slot = event.rawSlot
|
||||
if (slot !in 0..<size) return
|
||||
|
||||
when (slot) {
|
||||
navSlotPrev -> handlePrevPage()
|
||||
navSlotNext -> handleNextPage()
|
||||
navSlotSearch -> handleSearchClick()
|
||||
navSlotClear -> handleClearSearch()
|
||||
in 0 until kitSlots -> handleKitClick(slot, event.click)
|
||||
navSlotPrev -> handlePrevPage()
|
||||
navSlotNext -> handleNextPage()
|
||||
navSlotSearch -> handleSearchClick()
|
||||
navSlotClear -> handleClearSearch()
|
||||
in 0 until kitSlots -> handleKitClick(slot, event.click)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Kit-Interaktionen
|
||||
// -------------------------------------------------------------------------
|
||||
// ── Kit-Interaktionen ─────────────────────────────────────────────────────
|
||||
|
||||
private fun handleKitClick(slot: Int, clickType: ClickType) {
|
||||
val kit = getKitAtSlot(slot) ?: return
|
||||
|
||||
when {
|
||||
// Linksklick → Kit auswählen
|
||||
clickType.isLeftClick -> selectKit(kit)
|
||||
// Rechtsklick → Playstyle toggeln (und Menü refreshen)
|
||||
clickType.isRightClick -> togglePlaystyle(kit)
|
||||
}
|
||||
}
|
||||
@@ -138,27 +129,27 @@ class KitSelectorMenu(
|
||||
private fun selectKit(kit: Kit) {
|
||||
plugin.kitManager.selectKit(player, kit)
|
||||
player.sendActionBar(
|
||||
MiniMessage.miniMessage().deserialize(
|
||||
"<green>Kit <white>${kit.displayName}</white> gewählt!</green>"
|
||||
lm.getComponent(
|
||||
player, "gui.kit_selector.kit_selected",
|
||||
mapOf(), mapOf("kit" to kit.displayName)
|
||||
)
|
||||
)
|
||||
// Menü aktualisieren um Auswahl-Glanz zu zeigen
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun togglePlaystyle(kit: Kit) {
|
||||
val current = plugin.kitManager.getSelectedPlaystyle(player)
|
||||
val next = when (current) {
|
||||
val next = when (current) {
|
||||
Playstyle.AGGRESSIVE -> Playstyle.DEFENSIVE
|
||||
Playstyle.DEFENSIVE -> Playstyle.AGGRESSIVE
|
||||
}
|
||||
plugin.kitManager.selectPlaystyle(player, next)
|
||||
|
||||
// Wenn dieses Kit gerade ausgewählt ist, Playstyle-Änderung übernehmen
|
||||
if (plugin.kitManager.getSelectedKit(player)?.id == kit.id) {
|
||||
player.sendActionBar(
|
||||
MiniMessage.miniMessage().deserialize(
|
||||
"<gold>Playstyle: <white>► ${next.displayName}</white>"
|
||||
lm.getComponent(
|
||||
player, "gui.kit_selector.playstyle_changed",
|
||||
mapOf("playstyle" to next.displayName)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -166,32 +157,20 @@ class KitSelectorMenu(
|
||||
refresh()
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Navigation
|
||||
// -------------------------------------------------------------------------
|
||||
// ── Navigation ────────────────────────────────────────────────────────────
|
||||
|
||||
private fun handlePrevPage() {
|
||||
if (currentPage > 0) {
|
||||
currentPage--
|
||||
refresh()
|
||||
}
|
||||
if (currentPage > 0) { currentPage--; refresh() }
|
||||
}
|
||||
|
||||
private fun handleNextPage() {
|
||||
val totalPages = ((getFilteredKits().size - 1) / kitSlots + 1).coerceAtLeast(1)
|
||||
if (currentPage < totalPages - 1) {
|
||||
currentPage++
|
||||
refresh()
|
||||
}
|
||||
if (currentPage < totalPages - 1) { currentPage++; refresh() }
|
||||
}
|
||||
|
||||
private fun handleSearchClick() {
|
||||
// Öffne das Anvil-Suchmenü; schließt dieses Menü temporär
|
||||
AnvilSearchMenu(
|
||||
player = player,
|
||||
returnMenu = this,
|
||||
initialText = searchQuery
|
||||
).open(player)
|
||||
AnvilSearchMenu(player = player, returnMenu = this, initialText = searchQuery)
|
||||
.open(player)
|
||||
}
|
||||
|
||||
private fun handleClearSearch() {
|
||||
@@ -200,23 +179,14 @@ class KitSelectorMenu(
|
||||
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) {
|
||||
searchQuery = query.trim()
|
||||
currentPage = 0
|
||||
open(player) // neu öffnen (Anvil hat das vorherige Inventory geschlossen)
|
||||
open(player)
|
||||
}
|
||||
|
||||
private fun getFilteredKits(): List<Kit> {
|
||||
@@ -225,19 +195,14 @@ class KitSelectorMenu(
|
||||
|
||||
val q = searchQuery.lowercase()
|
||||
return allKits.filter { kit ->
|
||||
// Suche im Plain-Text des Adventure-Components
|
||||
kit.id.lowercase().contains(q) ||
|
||||
net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer
|
||||
.plainText()
|
||||
.serialize(kit.displayName)
|
||||
.lowercase()
|
||||
.contains(q)
|
||||
.plainText().serialize(kit.displayName).lowercase().contains(q)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getKitAtSlot(slot: Int): Kit? {
|
||||
val filteredKits = getFilteredKits()
|
||||
val absoluteIndex = currentPage * kitSlots + slot
|
||||
return filteredKits.getOrNull(absoluteIndex)
|
||||
return getFilteredKits().getOrNull(absoluteIndex)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package club.mcscrims.speedhg.gui.menu
|
||||
|
||||
import net.kyori.adventure.text.Component
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.inventory.InventoryClickEvent
|
||||
@@ -25,7 +26,7 @@ import org.bukkit.inventory.Inventory
|
||||
*/
|
||||
abstract class Menu(
|
||||
protected val rows: Int,
|
||||
protected val title: String
|
||||
protected val title: Component
|
||||
) {
|
||||
protected val size: Int = rows * 9
|
||||
internal lateinit var inventory: Inventory
|
||||
@@ -56,7 +57,7 @@ abstract class Menu(
|
||||
* Erstellt ein Inventory mit dem korrekten [MenuHolder].
|
||||
* 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 inv = Bukkit.createInventory(holder, size, parsedTitle)
|
||||
holder.bind(inv)
|
||||
|
||||
@@ -95,6 +95,28 @@ scoreboard:
|
||||
- ""
|
||||
- "<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:
|
||||
backup:
|
||||
name: '<gradient:gold:#ff841f><bold>Backup</bold></gradient>'
|
||||
|
||||
Reference in New Issue
Block a user