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.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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 0–44 für Kits
|
private val kitSlots = 45 // Slots 0–44 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>'
|
||||||
|
|||||||
Reference in New Issue
Block a user