diff --git a/docs/commands.md b/docs/commands.md index 1addbfd..efcca30 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -62,8 +62,8 @@ plots: Typ zapisanej działki wybiera właściwe rozszerzanie niezależnie od aktualnego `claiming.mode`. Działka klasyczna otrzymuje kwadratowy segment. Działka chunkowa otrzymuje jeden wolny chunk przylegający bokiem do chunka, w którym stoi właściciel. -Oba warianty są obsługiwane przez panel; na zgodnym Paper używany jest natywny dialog, -a na starszym lub tłumaczonym kliencie GUI ekwipunkowe. +Oba warianty są obsługiwane przez GUI ekwipunkowe. Natywne dialogi są zarezerwowane +dla operacji wymagających wpisania tekstu, takich jak zmiana nazwy działki. Zakup chunkowy używa osobnego cennika `plots.chunks.expansion`. Cena następnego chunka to `price × priceMultiplier ^ liczba_dokupionych_chunków`. Obowiązują diff --git a/docs/dialogs.md b/docs/dialogs.md index 379a20c..b65e746 100644 --- a/docs/dialogs.md +++ b/docs/dialogs.md @@ -1,8 +1,8 @@ # Dialogi działek -PlotsX wybiera natywne dialogi, jeśli serwer udostępnia Paper Dialog API (wprowadzone w Paper 1.21.7). Na starszych serwerach, w tym 1.20.6 i 1.21.6, pozostają dotychczasowe menu ekwipunku i zmiana nazwy przez czat. Minimum w `paper-plugin.yml` pozostaje 1.20.6, a bytecode Java 21. +PlotsX używa natywnego dialogu wyłącznie wtedy, gdy interakcja wymaga wpisania tekstu, obecnie przy zmianie nazwy działki. Dialog jest wybierany, jeśli serwer udostępnia Paper Dialog API (wprowadzone w Paper 1.21.7). Na starszych serwerach, w tym 1.20.6 i 1.21.6, zmiana nazwy odbywa się przez czat. Minimum w `paper-plugin.yml` pozostaje 1.20.6, a bytecode Java 21. -Dialogi obejmują zmianę nazwy, potwierdzenie tworzenia, usuwania i rozszerzania działki oraz usunięcia członka i przekazania własności. Główne menu i listy pozostają menu ekwipunku. Istniejące komendy z kompletnymi argumentami zachowują dotychczasowe działanie. +Potwierdzenia tworzenia, usuwania i rozszerzania działki oraz usunięcia członka i przekazania własności zawsze pozostają w menu ekwipunku. Natywny dialog nie zastępuje ekranów, które nie wymagają wprowadzenia treści. Istniejące komendy z kompletnymi argumentami zachowują dotychczasowe działanie. ```yaml interactions: @@ -14,7 +14,7 @@ interactions: Nowe komunikaty znajdują się w sekcji `dialogs` plików językowych PL i EN. Nazwa ma maksymalnie 255 znaków, zgodnie ze schematem bazy. Czat i dialog stosują tę samą walidację, filtr CleanerX i kontrolę uprawnień. Zapis obecnej nazwy jest dozwolony. -Formularz jest związany z graczem, wygasa po 60 sekundach i może zostać zatwierdzony tylko raz. Kolejny formularz, otwarcie ekwipunku lub wyjście gracza unieważniają poprzednią sesję. Anulowanie nie wykonuje operacji. Przy tworzeniu działki zmiana pozycji X/Z, świata albo promienia wymaga ponownego wywołania `/claim`. Rozszerzenie nadal weryfikuje aktualność ceny i poziomu przed pobraniem pieniędzy. +Formularz jest związany z graczem, wygasa po 60 sekundach i może zostać zatwierdzony tylko raz. Kolejny formularz, otwarcie ekwipunku lub wyjście gracza unieważniają poprzednią sesję. Anulowanie nie wykonuje operacji. Adapter dialogowy jest ładowany refleksyjnie; wspólna fasada nie zawiera typów Dialog API. Błąd wyświetlenia przywraca obsługiwane menu lub czat. Przełączanie na dialog i obsługa odpowiedzi używają schedulera gracza. Nie jest to audyt zgodności całego pluginu z Folia — istniejące operacje bazy/cache i wizualizacje zawierają również starsze wywołania schedulerów. @@ -25,9 +25,9 @@ Automatyczne testy `DialogSessionsTest` obejmują stare formularze, obcego gracz Przed wydaniem należy wykonać testy w grze (nie są zastąpione przez testy jednostkowe): - Paper 1.20.6 i 1.21.6: start JAR bez błędów brakujących klas, wszystkie operacje w starym interfejsie. -- Paper 1.21.7, 1.21.11 i wspierane 26.x: każdy nowy ekran, zapis, Anuluj/Escape i timeout. +- Paper 1.21.7, 1.21.11 i wspierane 26.x: dialog zmiany nazwy, zapis, Anuluj/Escape i timeout; pozostałe operacje w menu ekwipunku. - `mode: legacy` oraz konfiguracja translatora: działający fallback, w tym obsługa kliknięć w menu. - Odebranie uprawnień, usunięcie działki lub zmiana właściciela przy otwartym formularzu: odrzucenie nieaktualnej operacji. -- Rozszerzenie: zmiana ceny/poziomu, podwójne zatwierdzenie, brak środków i poprawne rozliczenie. +- Rozszerzenie w menu ekwipunku: zmiana ceny/poziomu, podwójne zatwierdzenie, brak środków i poprawne rozliczenie. - Zmiana nazwy: pusta/niedozwolona nazwa, duplikat, bieżąca nazwa i awaria filtra; błędy zachowują wpis w formularzu. - Folia: osobny test całych operacji z istniejącymi usługami pluginu. diff --git a/docs/wiki/Konfiguracja.md b/docs/wiki/Konfiguracja.md index baff9cb..4b54043 100644 --- a/docs/wiki/Konfiguracja.md +++ b/docs/wiki/Konfiguracja.md @@ -71,6 +71,20 @@ Zmiana początkowego promienia nie zmienia wielkości już utworzonych działek. Przycisk „Pokaż granice” w GUI rozszerzania zamyka menu i pokazuje obrys istniejącej działki. Czas ustawia `plots.borderDisplaySeconds: 30` (sekundy, minimum 1); obowiązuje również w głównym panelu działki. Podgląd nie kupuje segmentu. +## Powiadomienia o granicach działek + +Informacje o zbliżaniu się do działki, wejściu na nią i jej opuszczeniu są domyślnie +wyświetlane na pasku akcji, dzięki czemu nie zapełniają historii czatu: + +```yaml +plots: + notifications: + boundary: actionbar +``` + +Ustaw `boundary: chat`, jeśli te trzy rodzaje powiadomień mają trafiać na czat. +Niepoprawna wartość jest bezpiecznie traktowana jak `actionbar`. + Cena kolejnych rozszerzeń rośnie według `price × priceMultiplier ^ liczba_zakupionych_segmentów`. Domyślny `plots.expansion.priceMultiplier: 1.5` daje ceny 500, 750, 1125, 1687.5. Mnożnik musi wynosić co najmniej 1; wartość 1 wyłącza wzrost. Licznik jest osobny dla każdej działki, uwzględnia istniejące segmenty i pozostaje po restarcie lub przekazaniu działki. Pierwotny teren i nieudane zakupy nie zwiększają licznika. Zmiana ceny bazowej lub mnożnika przelicza cenę następnego zakupu. Nieprawidłowe wartości lub wynik przekraczający zakres obsługiwany przez ekonomię blokują zakup. Nie używa się już `levels` ani `step`. Przy aktualizacji usuń `levels` i ustaw `price: 500.0` lub własną cenę. Istniejące działki zachowują teren, a ich aktualny rozmiar staje się rozmiarem bazowym. Limity powierzchni liczą sumę segmentów; limit promienia określa najdalszą granicę względem pierwotnego środka. Szczegóły: [rozszerzanie i opłaty](../commands.md#rozszerzanie-działki-i-opłaty). diff --git a/docs/wiki/Panel-dzialki.md b/docs/wiki/Panel-dzialki.md index 36e4c60..04afd3f 100644 --- a/docs/wiki/Panel-dzialki.md +++ b/docs/wiki/Panel-dzialki.md @@ -24,7 +24,7 @@ Na serwerze z dostępnym Paper Dialog API (od 1.21.7) opcja otwiera formularz z Na starszych wersjach lub przy wymuszonym starym interfejsie masz 60 sekund na wpisanie nazwy na czacie. Wiadomość posłuży jako nazwa, zamiast trafić na czat ogólny. Gdy czas minie, otwórz opcję ponownie. Maksymalna długość nazwy wynosi 255 znaków. -Dialogi obsługują także potwierdzenia tworzenia, usuwania i rozszerzania działek oraz usuwania członków i przekazywania własności. Szczegóły wyboru interfejsu i obsługi translatorów protokołu opisuje [konfiguracja dialogów](../dialogs.md). +Pozostałe operacje, w tym tworzenie, usuwanie i rozszerzanie działek oraz zarządzanie członkami, używają menu ekwipunku. Dialog jest przeznaczony wyłącznie dla interakcji wymagających wpisania tekstu. Szczegóły wyboru interfejsu i obsługi translatorów protokołu opisuje [konfiguracja dialogów](../dialogs.md). Nie możesz nadać dwóch swoim działkom tej samej nazwy. Wybieraj proste nazwy, na przykład `Baza`, `Farma` i `Port`. Unikaj nazw `add`, `remove` i `members`, bo są używane przez komendy. diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/commands/UnclaimCMD.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/commands/UnclaimCMD.kt index 4e304dc..9d522e1 100644 --- a/src/main/kotlin/pl/syntaxdevteam/plotsx/commands/UnclaimCMD.kt +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/commands/UnclaimCMD.kt @@ -45,7 +45,6 @@ class UnclaimCMD(private val plugin: PlotsX) : BasicCommand { private fun openUnclaimGui(player: Player, plotId: Int) { val gui = UnclaimConfirmGUI( plugin = plugin, - plotName = plugin.databaseHandler.getPlotById(plotId)?.name ?: plotId.toString(), player = player, onConfirm = { p -> val current = plugin.databaseHandler.getPlotById(plotId) diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/ChunkExpandGUI.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/ChunkExpandGUI.kt index d46f531..f0a221c 100644 --- a/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/ChunkExpandGUI.kt +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/ChunkExpandGUI.kt @@ -30,30 +30,7 @@ internal class ChunkExpandGUI(private val plugin: PlotsX, private val plotId: In override fun open(player: Player) { val plot = prepareOffer(player) ?: return renderInventory(player, plot) - val geometry = plot.geometry as ChunkGeometry - val availableDirections = ExpansionDirection.entries - .filter { value -> source?.let { geometry.expansion(value, it) }?.let { candidate -> - plugin.cacheManager.getCachedPlots().none { other -> - other.id != plot.id && other.world.equals(plot.world, true) && - other.geometry.intersects(ChunkGeometry(setOf(candidate))) - } && plugin.regionProtectionHook?.overlapsBounds(player.world, candidate.bounds) != true - } == true } - .associate { it.name to text("expand.directions.${it.name.lowercase()}") } - val body = listOf( - if (source == null) text("expand.stand_inside") else text("expand.source", mapOf( - "x" to source!!.x.toString(), "z" to source!!.z.toString())), - text("expand.chunk_count", mapOf("count" to plot.chunks.size.toString(), - "max" to plugin.hookHandler.getMaxChunksPerPlot(player).toString())), - text("expand.price", mapOf("price" to (price?.toPlainString() ?: "?"))), - plugin.interactions.text("chunk_expand_choose") - ) - if (!plugin.interactions.choose(player, plugin.interactions.text("chunk_expand_title"), body, - availableDirections, onChoice = { selected, value -> - direction = ExpansionDirection.valueOf(value) - val refreshed = prepareOffer(selected) ?: return@choose - renderInventory(selected, refreshed) - openNativeConfirmation(selected, refreshed) - }, fallback = { openLegacy(player) })) openLegacy(player) + openLegacy(player) } private fun prepareOffer(player: Player): PlotData? { @@ -82,20 +59,6 @@ internal class ChunkExpandGUI(private val plugin: PlotsX, private val plotId: In inventory.setItem(24, item(Material.BARRIER, "expand.cancel")) } - private fun openNativeConfirmation(player: Player, plot: PlotData) { - val to = target ?: run { player.sendMessage(text("expand.direction_blocked")); open(player); return } - val body = listOf( - text("expand.directions.${direction.name.lowercase()}"), - text("expand.chunk_target", mapOf("x" to to.x.toString(), "z" to to.z.toString())), - text("expand.chunk_count", mapOf("count" to plot.chunks.size.toString(), - "max" to plugin.hookHandler.getMaxChunksPerPlot(player).toString())), - text("expand.price", mapOf("price" to (price?.toPlainString() ?: "?"))) - ) - if (!plugin.interactions.confirm(player, plugin.interactions.text("chunk_expand_confirm"), body, - onConfirm = { if (!submitted) { submitted = true; submit(it) } }, - onCancel = { open(it) }, fallback = { openLegacy(player) })) openLegacy(player) - } - private fun openLegacy(player: Player) { plugin.guiHandler.openLegacy(player, this) } override fun handleClick(event: InventoryClickEvent) { diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/ClaimConfirmGUI.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/ClaimConfirmGUI.kt index 52ce038..a985dbb 100644 --- a/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/ClaimConfirmGUI.kt +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/ClaimConfirmGUI.kt @@ -25,10 +25,7 @@ class ClaimConfirmGUI( "world" to loc.world.name, "x" to loc.blockX.toString(), "z" to loc.blockZ.toString(), "radius" to plugin.hookHandler.getClaimRadius(player).toString())) inventory.setItem(13, plugin.guiHandler.createItem(Material.MAP, body)) - if (!plugin.interactions.confirm(player, - message.stringMessageToComponentNoPrefix("GUI", "claim.title_claim"), listOf(body), - onConfirm = { submit(it, true) }, onCancel = { submit(it, false) }, - fallback = { openLegacy(player) })) openLegacy(player) + openLegacy(player) } private fun openLegacy(player: Player) { plugin.guiHandler.openLegacy(player, this) } diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/MembersGUI.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/MembersGUI.kt index 1be75ed..5469e8b 100644 --- a/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/MembersGUI.kt +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/MembersGUI.kt @@ -159,15 +159,6 @@ class MembersGUI( else -> show(it) } } - if (screen in listOf("remove", "transfer") && target != null && - plugin.interactions.confirm(player, - plugin.messageHandler.stringMessageToComponentNoPrefix("members", "confirm_$screen", - mapOf("player" to operations.name(target))), - listOf(plugin.interactions.text("${screen}_body", mapOf( - "plot" to plot.name, "player" to operations.name(target)))), - onConfirm = { run(it, if (screen == "transfer") listOf("transfer", target.toString(), "confirm") - else listOf("remove", target.toString())) }, - onCancel = { show(it) }, fallback = { openLegacy(player) })) return openLegacy(player) } diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/UnclaimConfirmGUI.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/UnclaimConfirmGUI.kt index 9fcea8f..ddfde65 100644 --- a/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/UnclaimConfirmGUI.kt +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/gui/UnclaimConfirmGUI.kt @@ -9,8 +9,7 @@ class UnclaimConfirmGUI( private val plugin: PlotsX, private val player: Player, private val onConfirm: (Player) -> Unit, - private val onCancel: (Player) -> Unit, - private val plotName: String = "" + private val onCancel: (Player) -> Unit ) : AbstractGUI(plugin.messageHandler.stringMessageToComponentNoPrefix("GUI", "unclaim.title_unclaim"), 3*9) { private val message = plugin.messageHandler @@ -20,11 +19,7 @@ class UnclaimConfirmGUI( private var submitted = false override fun open(player: Player) { - if (!plugin.interactions.confirm(player, - message.stringMessageToComponentNoPrefix("GUI", "unclaim.title_unclaim"), - listOf(plugin.interactions.text("unclaim_body", mapOf("plot" to plotName))), - onConfirm = { submit(it, true) }, onCancel = { submit(it, false) }, - fallback = { openLegacy(player) })) openLegacy(player) + openLegacy(player) } private fun openLegacy(player: Player) { plugin.guiHandler.openLegacy(player, this) } diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/interaction/PaperDialogBackend.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/interaction/PaperDialogBackend.kt index 0845764..248e756 100644 --- a/src/main/kotlin/pl/syntaxdevteam/plotsx/interaction/PaperDialogBackend.kt +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/interaction/PaperDialogBackend.kt @@ -15,12 +15,12 @@ import java.time.Duration /** Loaded reflectively only when Paper's dialog API exists. */ @Suppress("UnstableApiUsage") class PaperDialogBackend : DialogBackend { - override fun show(player: Player, title: Component, body: List, initial: String?, - yes: Component, no: Component, reply: (String?) -> Unit) { + override fun showTextInput(player: Player, title: Component, body: List, initial: String, + yes: Component, no: Component, reply: (String?) -> Unit) { val options = ClickCallback.Options.builder().uses(1).lifetime(Duration.ofSeconds(60)).build() val accept = ActionButton.builder(yes).action(DialogAction.customClick({ response, audience -> if (audience is Player && audience.uniqueId == player.uniqueId) - reply(if (initial == null) "" else response.getText("name") ?: "") + reply(response.getText("name") ?: "") }, options)).build() val cancel = ActionButton.builder(no).action(DialogAction.customClick({ _, audience -> if (audience is Player && audience.uniqueId == player.uniqueId) reply(null) @@ -29,35 +29,12 @@ class PaperDialogBackend : DialogBackend { builder.empty().base(DialogBase.builder(title) .canCloseWithEscape(true) .body(body.map { DialogBody.plainMessage(it) }) - .inputs(if (initial == null) emptyList() else listOf( - DialogInput.text("name", title).initial(initial.take(255)).maxLength(255).build())) + .inputs(listOf(DialogInput.text("name", title) + .initial(initial.take(255)).maxLength(255).build())) .build()) .type(DialogType.confirmation(accept, cancel)) } player.showDialog(dialog) } - override fun showChoices(player: Player, title: Component, body: List, - choices: Map, cancel: Component, - reply: (String?) -> Unit) { - val buttons = choices.map { (value, label) -> - ActionButton.builder(label).action(DialogAction.customClick({ _, audience -> - if (audience is Player && audience.uniqueId == player.uniqueId) reply(value) - }, callbackOptions())).build() - } - val cancelButton = ActionButton.builder(cancel).action(DialogAction.customClick({ _, audience -> - if (audience is Player && audience.uniqueId == player.uniqueId) reply(null) - }, callbackOptions())).build() - val dialog = Dialog.create { builder -> - builder.empty().base(DialogBase.builder(title) - .canCloseWithEscape(true) - .body(body.map { DialogBody.plainMessage(it) }) - .build()) - .type(DialogType.multiAction(buttons, cancelButton, 2)) - } - player.showDialog(dialog) - } - - private fun callbackOptions(): ClickCallback.Options = - ClickCallback.Options.builder().uses(1).lifetime(Duration.ofSeconds(60)).build() } diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/interaction/PlotInteractions.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/interaction/PlotInteractions.kt index d9f7d82..57b1221 100644 --- a/src/main/kotlin/pl/syntaxdevteam/plotsx/interaction/PlotInteractions.kt +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/interaction/PlotInteractions.kt @@ -10,10 +10,8 @@ import pl.syntaxdevteam.plotsx.PlotsX /** No dialog API types may escape the optional adapter into this baseline facade. */ interface DialogBackend { - fun show(player: Player, title: Component, body: List, initial: String?, - yes: Component, no: Component, reply: (String?) -> Unit) - fun showChoices(player: Player, title: Component, body: List, choices: Map, - cancel: Component, reply: (String?) -> Unit) + fun showTextInput(player: Player, title: Component, body: List, initial: String, + yes: Component, no: Component, reply: (String?) -> Unit) } class PlotInteractions(private val plugin: PlotsX) : Listener { @@ -35,22 +33,6 @@ class PlotInteractions(private val plugin: PlotsX) : Listener { .none { plugin.server.pluginManager.isPluginEnabled(it) } && !plugin.config.getBoolean("interactions.translated-clients", false) - fun confirm(player: Player, title: Component, body: List, yes: Component = text("confirm"), - no: Component = text("cancel"), onConfirm: (Player) -> Unit, onCancel: (Player) -> Unit = {}, - fallback: () -> Unit): Boolean = - show(player, title, body, null, yes, no, { p, value -> - if (value != null) onConfirm(p) else onCancel(p) - }, fallback) - - fun choose(player: Player, title: Component, body: List, choices: Map, - onChoice: (Player, String) -> Unit, onCancel: (Player) -> Unit = {}, - fallback: () -> Unit): Boolean { - if (choices.isEmpty()) return false - return showSession(player, fallback, { reply -> - backend!!.showChoices(player, title, body, choices, text("cancel"), reply) - }) { p, value -> if (value == null) onCancel(p) else onChoice(p, value) } - } - fun rename(player: Player, plotId: Int, initial: String, error: Component? = null): Boolean = show(player, text("rename"), listOfNotNull(error), initial, text("save"), text("cancel"), { p, value -> if (value != null) { @@ -61,10 +43,10 @@ class PlotInteractions(private val plugin: PlotsX) : Listener { } }, { plugin.renamePlotChatListener.startRenameProcess(player, plotId) }) - private fun show(player: Player, title: Component, body: List, initial: String?, + private fun show(player: Player, title: Component, body: List, initial: String, yes: Component, no: Component, reply: (Player, String?) -> Unit, fallback: () -> Unit): Boolean = showSession(player, fallback, { callback -> - backend!!.show(player, title, body, initial, yes, no, callback) + backend!!.showTextInput(player, title, body, initial, yes, no, callback) }, reply) private fun showSession(player: Player, fallback: () -> Unit, diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/protection/PlotNotificationMode.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/protection/PlotNotificationMode.kt new file mode 100644 index 0000000..8786cf9 --- /dev/null +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/protection/PlotNotificationMode.kt @@ -0,0 +1,11 @@ +package pl.syntaxdevteam.plotsx.protection + +internal enum class PlotNotificationMode { + ACTIONBAR, + CHAT; + + companion object { + fun fromConfig(value: String?): PlotNotificationMode = + entries.firstOrNull { it.name.equals(value?.trim(), ignoreCase = true) } ?: ACTIONBAR + } +} diff --git a/src/main/kotlin/pl/syntaxdevteam/plotsx/protection/PlotProtectionListener.kt b/src/main/kotlin/pl/syntaxdevteam/plotsx/protection/PlotProtectionListener.kt index 0a601d6..38770df 100644 --- a/src/main/kotlin/pl/syntaxdevteam/plotsx/protection/PlotProtectionListener.kt +++ b/src/main/kotlin/pl/syntaxdevteam/plotsx/protection/PlotProtectionListener.kt @@ -1,5 +1,6 @@ package pl.syntaxdevteam.plotsx.protection +import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import org.bukkit.Material import org.bukkit.block.Block @@ -250,6 +251,13 @@ class PlotProtectionListener(private val plugin: PlotsX) : Listener { private fun projectilePlayer(projectile: Projectile): Player? = projectile.shooter as? Player + private fun notifyPlotBoundary(player: Player, component: Component) { + when (PlotNotificationMode.fromConfig(plugin.config.getString("plots.notifications.boundary"))) { + PlotNotificationMode.ACTIONBAR -> player.sendActionBar(component) + PlotNotificationMode.CHAT -> player.sendMessage(component) + } + } + /** * Zdarzenie wywoływane, gdy gracz wchodzi na działkę. * Sprawdza, czy gracz zmienił działkę i wysyła odpowiednie wiadomości. @@ -276,12 +284,14 @@ class PlotProtectionListener(private val plugin: PlotsX) : Listener { if (oldPlotId != null) { val oldPlot = plugin.cacheManager.getCachedPlots().firstOrNull { it.id == oldPlotId } if (oldPlot != null) { - player.sendMessage(message.stringMessageToComponent("plots", "leave_plot", mapOf("plot" to oldPlot.name))) + notifyPlotBoundary(player, + message.stringMessageToComponent("plots", "leave_plot", mapOf("plot" to oldPlot.name))) } } if (newPlot != null) { - player.sendMessage(message.stringMessageToComponent("plots", "enter_plot", mapOf("plot" to newPlot.name))) + notifyPlotBoundary(player, + message.stringMessageToComponent("plots", "enter_plot", mapOf("plot" to newPlot.name))) } } @@ -293,7 +303,7 @@ class PlotProtectionListener(private val plugin: PlotsX) : Listener { if (approachingPlot != null && approachingPlot.ownerUuid != uuid && approachingPlot.id != newPlotId) { if (approachingPlotWarnings[uuid] != approachingPlot.id) { approachingPlotWarnings[uuid] = approachingPlot.id - player.sendMessage( + notifyPlotBoundary(player, message.stringMessageToComponent("plots", "approaching_plot", mapOf("plot" to approachingPlot.name)) ) } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4dfff2e..846f568 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -8,7 +8,7 @@ config-version: 100 # If you would like to add your own language file, please contact us on Discord (https://discord.gg/Zk6mxv7eMh). language: "EN" -# auto: native dialogs when available; legacy: existing inventory menus and chat. +# auto: native text-entry dialogs when available; legacy: text entry through chat. interactions: mode: auto # Set true when a proxy translates client protocols (e.g. ViaVersion/Geyser). @@ -58,6 +58,9 @@ plots: world: ["world"] # Duration of Show borders in the plot and expansion menus (seconds, minimum 1). borderDisplaySeconds: 30 + notifications: + # Where approaching, entering and leaving plot notices appear: actionbar | chat. + boundary: actionbar expansion: # Classic plots only. Chunk expansion uses plots.chunks.expansion above. # Stand in the segment from which you want to expand; select an adjacent direction. diff --git a/src/main/resources/lang/messages_en.yml b/src/main/resources/lang/messages_en.yml index 6bbb841..1645323 100644 --- a/src/main/resources/lang/messages_en.yml +++ b/src/main/resources/lang/messages_en.yml @@ -400,7 +400,6 @@ flags: name: "Team Management" description: "Add or remove all members of your team from the plot." dialogs: - confirm: "Confirm" cancel: "Cancel" save: "Save" rename: "Plot name" @@ -409,12 +408,6 @@ dialogs: claim_body: "Create a plot in , at X: , Z: , radius: ?" claim_chunk_body: "Claim chunk , in ? Area: 256 blocks." claim_changed: "Your position or claim radius changed. Run /claim again." - chunk_expand_title: "Expand chunk plot" - chunk_expand_choose: "Choose an adjacent direction. The exact target and price will be confirmed next." - chunk_expand_confirm: "Confirm chunk purchase" - unclaim_body: "Delete plot ? Its protection will be removed." - transfer_body: "Transfer ownership of to ? You will lose owner privileges." - remove_body: "Remove from plot ?" help: header: " commands" diff --git a/src/main/resources/lang/messages_pl.yml b/src/main/resources/lang/messages_pl.yml index dd94883..6b03324 100644 --- a/src/main/resources/lang/messages_pl.yml +++ b/src/main/resources/lang/messages_pl.yml @@ -401,7 +401,6 @@ flags: name: "Zarządzanie drużyną" description: "Dodaj lub usuń wszystkich członków swojej drużyny z działki." dialogs: - confirm: "Potwierdź" cancel: "Anuluj" save: "Zapisz" rename: "Nazwa działki" @@ -410,12 +409,6 @@ dialogs: claim_body: "Utworzyć działkę w , X: , Z: , o promieniu ?" claim_chunk_body: "Zająć chunk , w świecie ? Powierzchnia: 256 bloków." claim_changed: "Pozycja lub promień działki uległy zmianie. Użyj ponownie /claim." - chunk_expand_title: "Rozszerz działkę chunkową" - chunk_expand_choose: "Wybierz sąsiedni kierunek. Dokładny cel i cena zostaną pokazane przed zakupem." - chunk_expand_confirm: "Potwierdź zakup chunka" - unclaim_body: "Usunąć działkę ? Jej ochrona zostanie wyłączona." - transfer_body: "Przekazać działkę graczowi ? Utracisz uprawnienia właściciela." - remove_body: "Usunąć gracza z działki ?" help: header: "Komendy " diff --git a/src/test/kotlin/pl/syntaxdevteam/plotsx/protection/PlotNotificationModeTest.kt b/src/test/kotlin/pl/syntaxdevteam/plotsx/protection/PlotNotificationModeTest.kt new file mode 100644 index 0000000..b12f749 --- /dev/null +++ b/src/test/kotlin/pl/syntaxdevteam/plotsx/protection/PlotNotificationModeTest.kt @@ -0,0 +1,16 @@ +package pl.syntaxdevteam.plotsx.protection + +import org.junit.Assert.assertEquals +import org.junit.Test + +class PlotNotificationModeTest { + @Test fun `actionbar is the default for missing and invalid configuration`() { + assertEquals(PlotNotificationMode.ACTIONBAR, PlotNotificationMode.fromConfig(null)) + assertEquals(PlotNotificationMode.ACTIONBAR, PlotNotificationMode.fromConfig("title")) + } + + @Test fun `configuration is parsed without case or surrounding whitespace`() { + assertEquals(PlotNotificationMode.ACTIONBAR, PlotNotificationMode.fromConfig(" ActionBar ")) + assertEquals(PlotNotificationMode.CHAT, PlotNotificationMode.fromConfig("CHAT")) + } +}