From 2770d28009bdd784aff7dee9f1f8f327de1e8510 Mon Sep 17 00:00:00 2001 From: Kazotaruu_ Date: Sat, 7 Nov 2026 15:01:16 +0100 Subject: [PATCH] :sparkles: Add PlayerDialogClickEvent Called when a player clicks a custom-click button of a dialog, before its actions run. Gives the dialog file name and the button key. Cancelling it prevents the actions from running. Co-Authored-By: Claude Opus 5.5 --- .../event/events/PlayerDialogClickEvent.java | 51 +++++++++++++++++++ .../action/ZCustomClickDialogAction.java | 15 ++++++ .../ZCustomClickDialogActionLoader.java | 5 +- changelog.md | 1 + 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 API/src/main/java/fr/maxlego08/menu/api/event/events/PlayerDialogClickEvent.java diff --git a/API/src/main/java/fr/maxlego08/menu/api/event/events/PlayerDialogClickEvent.java b/API/src/main/java/fr/maxlego08/menu/api/event/events/PlayerDialogClickEvent.java new file mode 100644 index 00000000..2a1718d2 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/event/events/PlayerDialogClickEvent.java @@ -0,0 +1,51 @@ +package fr.maxlego08.menu.api.event.events; + +import fr.maxlego08.menu.api.event.CancelledMenuEvent; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player clicks a {@code custom-click} button of a zMenu dialog, + * before the actions of the button are executed. + *

+ * Cancelling this event prevents the actions of the button from being executed. + */ +public class PlayerDialogClickEvent extends CancelledMenuEvent { + + private final Player player; + private final String dialogName; + private final String buttonName; + + public PlayerDialogClickEvent(@NotNull Player player, @NotNull String dialogName, @NotNull String buttonName) { + this.player = player; + this.dialogName = dialogName; + this.buttonName = buttonName; + } + + @Contract(pure = true) + @NotNull + public Player getPlayer() { + return this.player; + } + + /** + * @return the file name of the dialog, without the {@code .yml} extension + */ + @Contract(pure = true) + @NotNull + public String getDialogName() { + return this.dialogName; + } + + /** + * @return the configuration key of the clicked button, for example {@code button1} for + * {@code multi-actions.button1}, {@code yes} / {@code no} for a confirmation dialog, + * {@code notice} or {@code exit-button} + */ + @Contract(pure = true) + @NotNull + public String getButtonName() { + return this.buttonName; + } +} diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/action/ZCustomClickDialogAction.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/action/ZCustomClickDialogAction.java index 9c8704e8..ac8a9d95 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/action/ZCustomClickDialogAction.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/action/ZCustomClickDialogAction.java @@ -3,6 +3,7 @@ import fr.maxlego08.menu.api.MenuPlugin; import fr.maxlego08.menu.api.button.dialogs.DialogButton; import fr.maxlego08.menu.api.engine.InventoryEngine; +import fr.maxlego08.menu.api.event.events.PlayerDialogClickEvent; import fr.maxlego08.menu.api.requirement.Requirement; import fr.maxlego08.menu.api.utils.Placeholders; import fr.maxlego08.menu.api.utils.dialogs.ZDialogAction; @@ -21,17 +22,31 @@ public class ZCustomClickDialogAction implements ZDialogAction { private final int usageLimit; private final @Nullable TemporalAmount actionDurationLimit; private final Map enablePlaceholders; + private final @Nullable String dialogName; + private final @Nullable String buttonName; public ZCustomClickDialogAction(@NotNull List<@NotNull Requirement> requirements, int usageLimit, @Nullable TemporalAmount actionDurationLimit, Map enablePlaceholders) { + this(requirements, usageLimit, actionDurationLimit, enablePlaceholders, null, null); + } + + public ZCustomClickDialogAction(@NotNull List<@NotNull Requirement> requirements, int usageLimit, @Nullable TemporalAmount actionDurationLimit, Map enablePlaceholders, @Nullable String dialogName, @Nullable String buttonName) { this.requirements = requirements; this.usageLimit = usageLimit; this.actionDurationLimit = actionDurationLimit; this.enablePlaceholders = enablePlaceholders; + this.dialogName = dialogName; + this.buttonName = buttonName; } @Override public DialogAction build(@NotNull List<@NotNull DialogInput> inputs, @NotNull Player player, @NotNull MenuPlugin plugin, @NotNull InventoryEngine inventoryEngine, @Nullable DialogButton button, @NotNull Placeholders placeholders) { return plugin.getDialogManager().clickAction(player, (clicker, view) -> { + if (this.dialogName != null && this.buttonName != null) { + PlayerDialogClickEvent event = new PlayerDialogClickEvent(clicker, this.dialogName, this.buttonName); + event.call(); + if (event.isCancelled()) return; + } + for (DialogInput input : view == null ? List.of() : inputs) { String key = input.key(); String value = null; diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/loader/dialogaction/ZCustomClickDialogActionLoader.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/loader/dialogaction/ZCustomClickDialogActionLoader.java index e643256b..c34149d9 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/loader/dialogaction/ZCustomClickDialogActionLoader.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/loader/dialogaction/ZCustomClickDialogActionLoader.java @@ -74,6 +74,9 @@ public class ZCustomClickDialogActionLoader implements DialogActionLoader { } } } - return new ZCustomClickDialogAction(actions, usageLimit, actionDurationLimit, enablePlaceholders); + String fileName = file.getName(); + String dialogName = fileName.endsWith(".yml") ? fileName.substring(0, fileName.length() - 4) : fileName; + String buttonName = path.contains(".") ? path.substring(path.lastIndexOf('.') + 1) : path; + return new ZCustomClickDialogAction(actions, usageLimit, actionDurationLimit, enablePlaceholders, dialogName, buttonName); } } diff --git a/changelog.md b/changelog.md index edf3e854..a4089777 100644 --- a/changelog.md +++ b/changelog.md @@ -25,6 +25,7 @@ ## New Features +* Added `PlayerDialogClickEvent`, called when a player clicks a `custom-click` button of a dialog, before its actions run. It gives the dialog file name and the button key (`button1`, `yes`, `no`, `notice`, `exit-button`...). Cancelling it prevents the actions of the button from running. * Added dialog canvases: the `dialog-canvas` body draws images and text at pixel positions inside a dialog (Paper 1.21.6+). Images come from zMenu's `resource-pack/textures` folder, from the vanilla client (`minecraft:item/diamond`), or from Nexo glyphs, ItemsAdder font images and CraftEngine images; text uses the vanilla font or the TrueType and bitmap fonts of `resource-pack/fonts.yml`, can be aligned in a box and is cut with `...` when too long. The `button` element adds a click area with an optional image, label and tooltip; it runs zMenu actions, checks `click-requirement` and `view-requirement` like inventory buttons, and can open the dialog again with `refresh: true`. A dialog whose canvas has a button stays open after a click unless it sets `after-action`, and button clicks expire after `dialog-canvas.click-lifetime-seconds`. The `panel` element stretches a texture to any size by nine-slice, the `progress` element draws the filled part of a bar from a value and a maximum, and every position can be a placeholder (a placeholder `y` lists its possible values in `y-values`). The `row` and `column` elements lay their children out for you, with `fit`, `grow`, fixed or percentage sizes, padding, gap, alignment and `justify` (example: `dialogs/canvas-layout-dialog.yml`). Placeholders (zMenu's and PlaceholderAPI's) also work in the canvas `width` and `height`, a text's `width` and `align`, a head's `texture` and `hat`, and a sprite's `atlas` and `sprite`. A `text` with `max-lines` (or `height`) becomes a box: its text, placeholders included, is broken into lines that fit its `width` (`wrap: word` or `char`), placed with `vertical-align`, and cut with `...` when too long (`overflow`); in a `column`, `layout-height: grow-text` makes the box as tall as its lines and moves what follows. Images of `resource-pack/textures`, panels and progress bars larger than 256 pixels in either direction are cut into a grid of tiles and drawn as one image, so a panel can be as high as the canvas. Multi-row Nexo glyphs and CraftEngine grid images are drawn whole, row under row, and the `head` and `sprite` elements draw a player head or a client atlas sprite (8×8, on a text line, Minecraft 1.21.9+). zMenu generates the fonts the canvases need into `plugins/zMenu/resource-pack` and adds them to the resource pack of Nexo, ItemsAdder or CraftEngine, or sends its own pack from a URL you upload it to (`standalone`), chosen with the new `dialog-canvas` section of `config.yml`. Players without the pack see the body's `fallback` text instead, chosen by `dialog-canvas.require-pack`: `auto` (default) waits for the standalone pack to load, but only shows the fallback after a failure for packs of Nexo, ItemsAdder, CraftEngine or `server.properties`, which are sent while the player joins; zMenu also sees the packs declined or failed at that moment, without needing PacketEvents. zMenu keeps the answer for each pack, so their order does not matter: with `standalone` only zMenu's pack counts, otherwise one pack declined or failed shows the fallback. The keys that choose what the pack holds (`glyph` of images and buttons, `font` of texts and buttons, `texture` of panels, `texture` and `background` of progress bars) accept a placeholder with the list of values it can give (`glyph-values: [default, vip, mvp]` for `glyph: "ranks/%luckperms_primary_group_name%"`, or the full values), and an optional `glyph-default` drawn for any other result; zMenu warns when one element needs more than 256 copies in the pack, and the generation message lists how many each dialog needs. In canvas text, button labels and tooltips, the MiniMessage tags a placeholder gives are shown as text, so a nickname or a suffix a player writes cannot add colors, fonts or clicks; `escape-placeholders: false` reads them as tags. A placeholder used by several keys of a canvas is resolved once per opening. Canvases can keep values while the player clicks, in memory only (never in player data): the new actions `canvas-set`, `canvas-toggle`, `canvas-cycle`, `canvas-add`, `canvas-remove` and `canvas-refresh` change them (`keep: open`, the default, forgets them when the dialog is opened again from outside; `keep: session` keeps them until the player leaves), `%canvas_%` reads them anywhere in the dialog (canvas, inputs, footer buttons; empty when unset, so `%canvas_tab.default:shop%` works) and `%zmenu_canvas_

:%` anywhere else; `/zmenu dialogs canvas ` lists them (example: `dialogs/canvas-tests/canvas-state.yml`). New canvas widgets keep their value in that state and re-open the dialog after each click: `toggle` (true/false with two glyphs and a label), `tabs` (options drawn as stretched panels, with `pages` of elements shown only for the selected option), `radio` (options drawn as a glyph and a label), `stepper` (a number with minus and plus, grey at its limits) and `slider` (a number chosen by clicking along a track, the knob costs nothing in the pack); every widget takes `key`, `default`, `keep`, `enabled`, `hover` and `on-change` (with `%value%` and `%previous%`) (example: `dialogs/canvas-tests/canvas-widgets.yml`, textures in `resource-pack/textures/widgets`). Two more widgets are drawn over every other element and take the clicks first: `dropdown` (a box that opens a list of options under it, with pages when there are more than `max-visible`; a click outside closes it) and `modal` (a window shown while its key is `true`, over an optional dark layer, with its own elements; nothing behind it can be clicked, a click outside closes it) (example: `dialogs/canvas-tests/canvas-overlays.yml`). Addon elements can draw over the others with `CanvasElement#renderOverlay` (and must answer `true` to `CanvasElement#hasOverlay`; the canvas skips the overlay pass of every other element). The click areas of a player's canvases are kept in one table per player, which opening a dialog again replaces (`CanvasManager#resetClicks`): the clicks of an older copy of a dialog stop working at once, and a player clicking widgets no longer piles up click callbacks on the server. Inside widgets and lists, the placeholders a canvas has already resolved are reused for every value, entry and page. Each canvas element is now drawn on the text line its highest image or text falls on, so the pack only holds the y inside that line: the same image 9 pixels lower, or two elements at y 10 and y 19, share one copy instead of two. The lines never go back, so the drawing order stays the order of the elements, and the overlay pass (dropdown lists, windows) stays above everything; click areas stay on the first line. Addon elements that draw in `renderOverlay` mark it with `CanvasVariants#beginOverlay` and `#endOverlay` in `collectVariants`. Every piece a widget draws can now be moved and styled: a part is still written as a value (`label: "Music"`) or as a section with `text` or `glyph` (and `glyph-disabled`), `x` and `y` relative to the widget, `width`, `align`, `font` and its own `hover`. The parts are the toggle's `label`, `label-on` and `label-off`; the stepper's `minus`, `plus`, `text`, `text-min` and `text-max`; the slider's `knob`, `text`, `text-min` and `text-max`; the confirm button's `text` and `text-armed`; the dropdown's `text` (with `%label%`), `arrow`, `list` (where the open list appears) and `option-text`; and the `label` and `label-selected` (`label-checked`) of tabs, radio and checklist, placed inside each option. The options of tabs, radio and checklist take their own `x` and `y` (they then leave the row or column, so options can form any shape), their own looks (`panel-selected` / `panel-idle`, `glyph-selected` / `glyph-idle`, `glyph-on` / `glyph-off`), a `label-selected` (`label-checked`) and a `hover`; dropdown options take `{ label, hover }`; the rating takes `star-hover` and `stars` (each star at its own position). Without these keys every widget looks as before (example: `dialogs/canvas-tests/canvas-custom.yml`). When the dialogs load, zMenu also warns about widgets going past the canvas, options or list rows closer than 9 pixels (older clients find clicks by lines of 9 pixels), text that does not fit inside tab pages and windows, and a `canvas-refresh` in the button Escape runs (the only button of a notice, the "no" of a confirmation, an exit button), which would open the dialog again instead of closing it. Experimental `dialog-canvas.hide-focus-outline` (off by default) adds to the pack the client's `gui` shaders, patched to skip white 1 pixel lines exactly as long as the outline of a canvas, which hides the white outline a click draws around a canvas body and leaves the rest of the client's screens alone. The `list` widget shows entries (a YAML list, or a placeholder giving a list) a page at a time, each drawn with a template element placed in a slot with `%entry%`, `%index%` and `%number%`, and `pages` shows hand-written pages of elements one at a time; both have previous and next buttons and a page text with `%page%` and `%pages%` (example: `dialogs/canvas-tests/canvas-list.yml`). The `item` element draws an item (its glyph, `minecraft:item/` by default, and its amount) with the item's real tooltip on hover, built with the zMenu item format, and runs actions like a button; a `list` lays its slots out in a grid with `columns` and keeps only the entries containing a text with `filter` (a search typed in a dialog input, for example); the new `checklist` widget checks several options at once (its value is a comma list such as `pvp,shop`, with an optional `max`), `confirm` asks for a second click before running its actions, `rating` fills a row of stars up to the one clicked, and `pages` becomes a wizard with `next-requirement`, a condition per page to go further; `visible` also accepts `has` / `!has` (a comma list holds a value) and `contains` / `!contains` (a text holds another, any case) (examples: `dialogs/canvas-tests/canvas-shop.yml` and `canvas-wizard.yml`). A value a player typed and stored with `canvas-set` is never resolved by PlaceholderAPI. `visible` also accepts comparisons such as `"%canvas_tab% == shop"` or `"%player_level% >= 10"`. Canvases now see the placeholders of the dialog opening (`%player%`, input values). Every canvas element can be hidden per player with `visible` (a placeholder giving `true`, `yes` or `1`) or `view-requirement` (zMenu requirements, as on inventory buttons); inside a `row` or `column`, a hidden child keeps its space unless it sets `collapse: true`, and then the children after it take its place (at most 6 such children per layout, since the pack holds every combination). A canvas without placeholders, buttons, heads or sprites is drawn once and reused for every player until the pack is generated again; addon elements opt in with `CanvasElement#isStatic`. Bedrock players (Geyser and Floodgate) always get the fallback. When the dialogs load, zMenu warns once per dialog about canvas mistakes: missing glyphs, unknown fonts, images, text or buttons outside the canvas, and fixed text cut because it does not fit its `width` or `max-lines`. The new `/zmenu dialogs canvas [player]` command (permission `zmenu.dialog.canvas`) shows the pack target, the pack, how many canvases are drawn once for every player, these problems, and for a player their client version, Bedrock or not, their resource pack status and whether they see canvases. Example: `dialogs/canvas-dialog.yml`, and one dialog per feature in `dialogs/canvas-tests` (open the list with `/zmenu dialogs open canvas-tests`), which work without PlaceholderAPI through player data (`canvas_*` keys in `default_values.yml`). These examples are in the zMenu jar and are not saved in `plugins/zMenu/dialogs`, since a loaded canvas makes zMenu generate a pack and add it to the server's pack plugin: copy the ones you want into `dialogs`. The vanilla client's font and textures (`dialog-canvas.download-vanilla-assets`) are only downloaded once a loaded canvas needs them: text in the default font, a `minecraft:` image or `hide-focus-outline`. * Dialog canvases measure the vanilla font and `minecraft:` images from the pack of Nexo, ItemsAdder or CraftEngine when that pack replaces them (a custom `font/ascii.png`, for example), so text and images line up with what players see. A Nexo glyph, ItemsAdder font image or CraftEngine image whose texture cannot be read is reported and skipped instead of being measured as a square. * Added `CanvasPackTarget#readPackFile`, so a pack target can give the files its pack puts over the client's own.