44import net .md_5 .bungee .api .ChatColor ;
55import org .broken .arrow .library .color .TextTranslator ;
66import org .broken .arrow .library .menu .utility .DuplicateMessage ;
7+ import org .broken .arrow .library .serialize .utility .converters .PlaceholderTranslator ;
78import org .bukkit .entity .Player ;
89import org .bukkit .inventory .ItemStack ;
910
@@ -89,64 +90,71 @@ public void setBlacklistMessage(final Function<ItemStack, String> blacklistMessa
8990 }
9091
9192 /**
92- * Set message for when player have added item some are duplicated.
93- * Support both hex and & color codes.
93+ * Sets a static message format for duplicated item handling.
9494 *
95- * <p><b>Supported color formats:</b></p>
95+ * <p>This is a convenience method equivalent to:</p>
96+ * <pre>
97+ * setDuplicatedMessage(wrapper -> "your message");
98+ * </pre>
99+ *
100+ * <p>The message supports placeholders and color codes and will be processed automatically.</p>
101+ *
102+ * <p><b>Formatting support:</b></p>
96103 * <ul>
97- * <li>Hex colors: {@code <#8000ff>} or gradients like {@code <#8000ff:#ff0080>} (requires color conversion module).</li>
98- * <li>Fallback to Spigot's legacy format: {@code &x&6&6&6&6&6&6}, or for white: {@code &x&F&F&F&F&F&F}, if color conversion is not enabled.</li>
104+ * <li>Legacy color codes: {@code &a}, {@code &f}, etc.</li>
105+ * <li>Legacy hex format (Spigot-compatible): {@code &x&R&R&G&G&B&B}</li>
106+ * <li>Hex colors: {@code <#RRGGBB>} and gradients like {@code <#RRGGBB:#RRGGBB>}
107+ * (supported when the internal formatter is available)</li>
99108 * </ul>
100109 *
101- * <p><b>Available placeholders:</b></p>
110+ * <p>Formatting is handled automatically using the best available implementation
111+ * (Spigot-compatible or internal formatter).</p>
112+ *
113+ * <p><b>Available indexed placeholders:</b></p>
102114 * <ul>
103- * <li>{@code {0}} – The item type (e.g., {@code DIAMOND_SWORD})</li>
104- * <li>{@code {1}} – Total number of duplicated stacks</li>
105- * <li>{@code {2}} – Total number of duplicated items </li>
115+ * <li>{@code {0}} – Item type (e.g. {@code DIAMOND_SWORD})</li>
116+ * <li>{@code {1}} – Total duplicated stacks</li>
117+ * <li>{@code {2}} – Total duplicated item amount </li>
106118 * </ul>
107119 *
108- * @param duplicatedMessage set a message.
120+ * <p><b>Note:</b> If {@link DuplicatedItemWrapper#getPlaceholderWrapper()} is used, those take priority over indexed placeholders.</p>
121+ *
122+ * @param duplicatedMessage message format string
109123 */
110124 public void setDuplicatedMessage (String duplicatedMessage ) {
111- this .duplicatedMessage = (itemStack , size , itemAmount ) -> duplicatedMessage ;
125+ this .duplicatedMessage = (wrapper ) -> duplicatedMessage ;
112126 }
113127
114128 /**
115- * Sets the message format to display when a player attempts to add items and some are duplicates .
129+ * Sets a custom message generator for duplicated item handling .
116130 *
117- * <p>
118- * Supports both hex color codes and legacy {@code &} codes. This function allows you to handle placeholder
119- * replacement yourself instead of using a predefined message format, giving you more dynamic control, particularly useful
120- * if you're using a localization file or want the text to be updated externally with minimal code changes.
121- * </p>
131+ * <p>This allows full control over the final message, including placeholders and formatting.
132+ * The provided function receives a {@link DuplicatedItemWrapper}, and the returned string
133+ * is processed automatically.</p>
122134 *
123- * <p><b>Supported color formats:</b></p>
135+ * <p>If not set, a default message format is used.</p>
136+ *
137+ * <p><b>Formatting support:</b></p>
124138 * <ul>
125- * <li>Hex colors: {@code <#8000ff>} or gradients like {@code <#8000ff:#ff0080>} (requires the color conversion module).</li>
126- * <li>Fallback to Spigot's legacy format: {@code &x&6&6&6&6&6&6}, or for white: {@code &x&F&F&F&F&F&F}, if color conversion is not enabled.</li>
139+ * <li>Legacy color codes: {@code &a}, {@code &f}, etc.</li>
140+ * <li>Legacy hex format (Spigot-compatible): {@code &x&R&R&G&G&B&B}</li>
141+ * <li>Hex colors: {@code <#RRGGBB>} and gradients like {@code <#RRGGBB:#RRGGBB>}
142+ * (supported when the internal formatter is available)</li>
127143 * </ul>
128144 *
129- * <p><b>Available placeholders:</b></p>
145+ * <p>Formatting is handled automatically using the best available implementation
146+ * (Spigot-compatible or internal formatter).</p>
147+ *
148+ * <p><b>Available indexed placeholders:</b></p>
130149 * <ul>
131- * <li>{@code {0}} – The item type (e.g., {@code DIAMOND_SWORD})</li>
132- * <li>{@code {1}} – Total number of duplicated stacks</li>
133- * <li>{@code {2}} – Total number of duplicated items </li>
150+ * <li>{@code {0}} – Item type (e.g. {@code DIAMOND_SWORD})</li>
151+ * <li>{@code {1}} – Total duplicated stacks</li>
152+ * <li>{@code {2}} – Total duplicated item amount </li>
134153 * </ul>
135154 *
136- * <p>
137- * The returned string will be further processed after this function is called — including color conversion
138- * and placeholder replacement (e.g., {@code {0}}, {@code {1}}, {@code {2}}). You only need to provide the
139- * base message with the desired format and placeholders.
140- * </p>
141- *
142- * <p>
143- * You may also choose to handle placeholder translation yourself by including resolved values directly
144- * in the lambda. If you want full control over the output, including color formatting, simply return
145- * an empty string or {@code null} to skip the default message generation and formatting.
146- * </p>
155+ * <p><b>Note:</b> If {@link DuplicatedItemWrapper#getPlaceholderWrapper()} is used, those take priority over indexed placeholders.</p>
147156 *
148- * @param duplicatedMessage a function that takes the item type, duplicated {@link ItemStack}, and finally duplicated item count,
149- * and returns the base message string to be processed.
157+ * @param duplicatedMessage function that generates the base message from duplicated item data.
150158 */
151159 public void setDuplicatedMessage (final DuplicateMessage duplicatedMessage ) {
152160 this .duplicatedMessage = duplicatedMessage ;
@@ -156,7 +164,7 @@ public void setDuplicatedMessage(final DuplicateMessage duplicatedMessage) {
156164 * Sends a raw message to the specified player.
157165 *
158166 * @param player the player to send the message to
159- * @param msg the message string to send
167+ * @param msg the message string to send
160168 */
161169 public void sendMessage (Player player , String msg ) {
162170 player .sendMessage (msg );
@@ -166,13 +174,17 @@ public void sendMessage(Player player, String msg) {
166174 * Sends the blacklist message to the player regarding the specified blacklisted item.
167175 * The message supports color codes and placeholder replacement.
168176 *
169- * @param player the player to send the message to
177+ * @param player the player to send the message to
170178 * @param itemStack the blacklisted item stack triggering the message
171179 */
172180 public void sendBlacklistMessage (Player player , ItemStack itemStack ) {
173181 String message ;
174- if (blacklistMessage == null ) message = "&fThis item&6 {0}&f are blacklisted and you get the items back." ;
175- else message = blacklistMessage .apply (itemStack .clone ());
182+ if (blacklistMessage == null ) {
183+ message = "&fThis item&6 {0}&f are blacklisted and you get the items back." ;
184+ } else {
185+ message = blacklistMessage .apply (itemStack .clone ());
186+ }
187+
176188 if (message == null || message .isEmpty ())
177189 return ;
178190
@@ -186,31 +198,38 @@ public void sendBlacklistMessage(Player player, ItemStack itemStack) {
186198 * Sends the duplicated item message to the player, using data from the provided placeholder wrapper.
187199 * The message supports color codes and placeholder replacement.
188200 *
189- * @param player the player to send the message to
201+ * @param player the player to send the message to
190202 * @param placeholderData wrapper containing duplicated item data for placeholders
191203 */
192204 public void sendDuplicatedMessage (@ Nonnull final Player player , @ Nonnull final DuplicatedItemWrapper placeholderData ) {
193205 String message ;
194206 if (duplicatedMessage == null ) {
195207 message = "&fYou can't add more if this &6 {0} &ftype, you get back &6 {2}&f items.You have added totally &4{1}&f extra itemstacks" ;
196208 } else {
197- message = duplicatedMessage .apply (placeholderData . getItemStack (). clone (), placeholderData . getSize (), placeholderData . getItemAmount () );
209+ message = duplicatedMessage .apply (placeholderData );
198210 }
199211
200212 if (message == null || message .isEmpty ())
201213 return ;
202214
215+ final PlaceholderTranslator .PlaceholderWrapper wrapper = placeholderData .getPlaceholderWrapper ();
216+ if (!wrapper .getPlaceholders ().isEmpty ())
217+ message = PlaceholderTranslator .translateText (message , wrapper );
218+ else {
219+ message = PlaceholderTranslator .translateText (message , placeholderData .retrieveAsPlaceholderData ());
220+ }
221+
203222 if (notFoundTextTranslator )
204- player .sendMessage (ChatColor .translateAlternateColorCodes ('&' , ( translatePlaceholders ( message , placeholderData . retrieveAsPlaceholderData ())) ));
223+ player .sendMessage (ChatColor .translateAlternateColorCodes ('&' , message ));
205224 else
206- player .sendMessage (TextTranslator .toSpigotFormat (translatePlaceholders ( message , placeholderData . retrieveAsPlaceholderData ()) ));
225+ player .sendMessage (TextTranslator .toSpigotFormat (message ));
207226 }
208227
209228 /**
210229 * Replaces placeholders of the form {@code {0}, {1}, ...} in the given text with
211230 * the string representation of the provided placeholder objects.
212231 *
213- * @param rawText the text containing placeholders to replace
232+ * @param rawText the text containing placeholders to replace
214233 * @param placeholders the objects to insert into the placeholders
215234 * @return the text with placeholders replaced by their corresponding values
216235 */
@@ -225,19 +244,20 @@ public String translatePlaceholders(String rawText, Object... placeholders) {
225244 * Wrapper class holding information about duplicated items for placeholder substitution.
226245 */
227246 public static class DuplicatedItemWrapper {
228-
247+ private final PlaceholderTranslator . PlaceholderWrapper placeholderWrapper ;
229248 private final ItemStack itemStack ;
230249 private final int size ;
231250 private final int itemAmount ;
232251
233252 /**
234253 * Constructs a new wrapper containing duplicated item data.
235254 *
236- * @param itemStack the duplicated item stack
237- * @param size the total number of duplicated stacks
255+ * @param itemStack the duplicated item stack
256+ * @param size the total number of duplicated stacks
238257 * @param itemAmount the total number of duplicated items
239258 */
240259 public DuplicatedItemWrapper (final ItemStack itemStack , final int size , final int itemAmount ) {
260+ this .placeholderWrapper = new PlaceholderTranslator .PlaceholderWrapper ();
241261 this .itemStack = itemStack ;
242262 this .size = size ;
243263 this .itemAmount = itemAmount ;
@@ -249,7 +269,7 @@ public DuplicatedItemWrapper(final ItemStack itemStack, final int size, final in
249269 * @return the item stack
250270 */
251271 public ItemStack getItemStack () {
252- return itemStack ;
272+ return itemStack . clone () ;
253273 }
254274
255275 /**
@@ -270,6 +290,39 @@ public int getItemAmount() {
270290 return itemAmount ;
271291 }
272292
293+ /**
294+ * Returns a {@link PlaceholderTranslator.PlaceholderWrapper} for defining custom
295+ * key-based placeholders instead of using the default indexed placeholder system.
296+ *
297+ * <p>If no custom placeholders are provided, the system falls back to
298+ * {@link #retrieveAsPlaceholderData()}, which uses ordered placeholders such as
299+ * {@code {0}}, {@code {1}}, {@code {2}}.</p>
300+ *
301+ * <p>When this wrapper contains entries, it is used instead and allows named
302+ * placeholders.</p>
303+ *
304+ * <p><b>Example (default indexed placeholders):</b></p>
305+ * <pre>
306+ * "&fYou can't add more if this &6 {0} &ftype, you get back &6 {2} &fitems.
307+ * You have added totally &4 {1} &fextra itemstacks"
308+ * </pre>
309+ *
310+ * <p><b>Example (custom named placeholders):</b></p>
311+ * <pre>
312+ * wrapper.put("{type}", itemStack.getType())
313+ * .put("{addedStacks}", size)
314+ * .put("{returnedItems}", itemAmount);
315+ *
316+ * "&fYou can't add more if this &6 {type} &ftype, you get back &6 {returnedItems} &fitems.
317+ * You have added totally &4 {addedStacks} &fextra itemstacks"
318+ * </pre>
319+ *
320+ * @return the {@link PlaceholderTranslator.PlaceholderWrapper} for custom placeholders
321+ */
322+ public PlaceholderTranslator .PlaceholderWrapper getPlaceholderWrapper () {
323+ return placeholderWrapper ;
324+ }
325+
273326 /**
274327 * Returns an array of objects to be used as placeholders in messages:
275328 * item type, duplicated stacks count, and duplicated items count.
0 commit comments