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_