Resolve grouping of unresolved types that likely should result in none > unsupported type#121
Resolve grouping of unresolved types that likely should result in none > unsupported type#121R00tB33rMan wants to merge 1 commit into
Conversation
…e > unsupported type
|
Hi, can you share a small example GUI that can be used to reproduce this issue? This exception would mean that the client sends the ContainerClickPacket with a different containerId. Depending on why this is it might make more sense to handle the click normally. |
Sure! Here's an example: /*
* RootBeer
*
* Copyright [2016] - [2026] RootBeer
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of RootBeer.
* The intellectual and technical concepts contained herein are proprietary to RootBeer
* and may be covered by applicable patents, patents in process, and are protected by
* trade secret or copyright law. Dissemination of this information or reproduction of
* this material is strictly forbidden unless prior written permission is obtained from
* RootBeer.
*/
package com.rootbeer.magnolia.module.shop.menu;
import com.rootbeer.magnolia.module.shop.config.ShopConfig;
import com.rootbeer.magnolia.module.shop.config.ShopMessages;
import com.rootbeer.magnolia.module.shop.loader.ShopLoader;
import com.rootbeer.magnolia.module.shop.manager.TransactionManager;
import com.rootbeer.magnolia.module.shop.object.ShopCategory;
import com.rootbeer.magnolia.module.shop.object.ShopItem;
import com.rootbeer.magnolia.util.menu.MenuUtil;
import com.xcodiq.localization.component.Components;
import com.xcodiq.paper.util.itemstack.ItemStackBuilder;
import com.xcodiq.paper.menu.item.ConfigurableItem;
import com.xcodiq.paper.menu.parameter.MenuParameter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import com.rootbeer.magnolia.menu.MagnoliaConfigurableMenu;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
public final class ShopAmountMenu extends MagnoliaConfigurableMenu {
private static final char ITEM_MARKER = 'i';
private final ShopCategory category;
private final ShopLoader shopLoader;
private final TransactionManager transactionManager;
private final ShopItem shopItem;
private final int stackSize;
private final int maxAmount;
private final int amount;
private final boolean selling;
public ShopAmountMenu(@NotNull ShopCategory category,
@NotNull ShopLoader shopLoader,
@NotNull TransactionManager transactionManager,
@NotNull ShopItem shopItem,
int amount,
boolean selling) {
super(selling
? "module/shop/menu/sell_amount_menu.yml"
: "module/shop/menu/amount_menu.yml", false);
this.category = category;
this.shopLoader = shopLoader;
this.transactionManager = transactionManager;
this.shopItem = shopItem;
this.stackSize = this.shopItem.item().getMaxStackSize();
this.selling = selling;
final int maxStacks = Math.min(ShopConfig.AMOUNT_MENU_MAX_STACKS.toInt(),
ShopConfig.AMOUNT_MENU_BUY_LIMIT_STACKS.toInt());
this.maxAmount = this.stackSize * maxStacks;
this.amount = Math.clamp(amount, 1, this.maxAmount);
this.structure.addIngredient(ITEM_MARKER, ConfigurableItem.builder()
.key("item_preview")
.withItem(_ -> {
final ItemStack preview = this.shopItem.displayItem().clone();
preview.setAmount(Math.min(this.amount, this.stackSize));
appendPreviewLore(preview);
MenuUtil.tag(preview);
return () -> preview;
})
.async()
.build());
registerStackButtons();
this.initialize();
this.feedbackButton("confirm",
player -> this.selling || this.transactionManager.canAfford(player, this.shopItem, this.amount),
this::confirm,
_ -> ShopMessages.BUY_FEEDBACK_REASON.toStr().replace("%price%", this.formatPriceForMode(this.amount)));
}
private void registerStackButtons() {
final int maxStacks = Math.min(ShopConfig.AMOUNT_MENU_MAX_STACKS.toInt(),
ShopConfig.AMOUNT_MENU_BUY_LIMIT_STACKS.toInt());
final String markers = ShopConfig.AMOUNT_MENU_STACKS_MARKERS.toStr();
final String nameTemplate = ShopConfig.AMOUNT_MENU_STACKS_NAME.toStr();
final List<String> loreTemplate = ShopConfig.AMOUNT_MENU_STACKS_LORE.toStringList();
final String materialOverride = ShopConfig.AMOUNT_MENU_STACKS_MATERIAL.toStr();
final Material overrideMaterial = materialOverride.equalsIgnoreCase("%item%")
? null
: Material.matchMaterial(materialOverride);
final boolean glint = ShopConfig.AMOUNT_MENU_STACKS_GLINT.toBoolean();
for (int s = 0; s < markers.length() && s < maxStacks; s++) {
final int stackCount = s + 1;
final int totalAmount = this.stackSize * stackCount;
final String formattedPrice = this.formatPriceForMode(totalAmount);
this.structure.addIngredient(markers.charAt(s), ConfigurableItem.builder()
.key("stacks_" + stackCount)
.withItem(_ -> {
final ItemStack display;
if (overrideMaterial != null) {
display = new ItemStack(overrideMaterial, stackCount);
} else {
display = this.shopItem.displayItem().clone();
display.setAmount(stackCount);
}
final List<String> loreLines = new ArrayList<>();
for (final String line : loreTemplate) {
loreLines.add(Components.prepareLine(line,
"%stacks%", String.valueOf(stackCount),
"%amount%", String.valueOf(totalAmount),
"%price%", formattedPrice));
}
final ItemStack result = ItemStackBuilder.of(display)
.apply(builder -> {
if (glint) {
builder.transformMeta(m -> m.setEnchantmentGlintOverride(true));
}
})
.name(Components.prepareLine(nameTemplate,
"%stacks%", String.valueOf(stackCount)))
.clearLore()
.lore(loreLines)
.build();
MenuUtil.tag(result);
return () -> result;
})
.onClick((player, _) -> {
if (MenuUtil.unacquireClick(player.getUniqueId())) {
return;
}
final int newAmount = this.stackSize * stackCount;
final int clamped = clamp(newAmount);
if (clamped == this.amount) {
MenuUtil.releaseClick(player.getUniqueId());
return;
}
openWithAmount(player, clamped);
})
.async()
.build());
}
}
@Override
public @NotNull MenuParameter[] setupMenuParameters() {
final int stacks = (int) Math.ceil((double) this.amount / this.stackSize);
return new MenuParameter[] {
MenuParameter.of("item", formatItemName(this.shopItem.item())),
MenuParameter.of("amount", String.valueOf(this.amount)),
MenuParameter.of("stacks", String.valueOf(stacks)),
MenuParameter.of("max_stack", String.valueOf(this.stackSize)),
MenuParameter.of("price", this.formatPriceForMode(this.amount))
};
}
/**
* Handles quantity selection and buy/sell confirmation.
* Supports preset amounts (1, 16, 32, 64) and custom input.
*/
@Override
public BiConsumer<Player, String> setupClick() {
return (player, key) -> {
if (MenuUtil.unacquireClick(player.getUniqueId())) {
return;
}
if (key.equals("cancel")) {
this.plugin.scheduler().now(player, () -> {
new ShopCategoryMenu(this.category, this.shopLoader, this.transactionManager)
.open(player);
MenuUtil.releaseClick(player.getUniqueId());
});
return;
}
final int newAmount = resolveAmount(key);
if (newAmount < 0 || clamp(newAmount) == this.amount) {
MenuUtil.releaseClick(player.getUniqueId());
return;
}
openWithAmount(player, newAmount);
};
}
@NotNull
private String formatPriceForMode(int amount) {
if (this.selling) {
return this.transactionManager.formatPrice(this.shopItem,
this.transactionManager.effectiveSellPrice(this.shopItem, amount));
}
return this.transactionManager.formatPrice(this.shopItem,
this.transactionManager.effectiveBuyPrice(this.shopItem, amount));
}
private int resolveAmount(@NotNull String key) {
if (key.equals("set_max")) {
return this.stackSize;
}
if (key.startsWith("set_")) {
final int value = parseTrailingInt(key, "set_");
if (value > 0) {
return value;
}
}
if (key.startsWith("add_")) {
final int value = parseTrailingInt(key, "add_");
if (value > 0) {
return this.amount + value;
}
}
if (key.startsWith("remove_")) {
final int value = parseTrailingInt(key, "remove_");
if (value > 0) {
return this.amount - value;
}
}
if (key.startsWith("stacks_")) {
final int value = parseTrailingInt(key, "stacks_");
if (value > 0) {
return this.stackSize * value;
}
}
return -1;
}
private int clamp(int value) {
return Math.clamp(value, 1, this.maxAmount);
}
private static int parseTrailingInt(@NotNull String key, @NotNull String prefix) {
try {
return Integer.parseInt(key.substring(prefix.length()));
} catch (NumberFormatException e) {
return -1;
}
}
private void appendPreviewLore(@NotNull ItemStack item) {
final int stacks = (int) Math.ceil((double) this.amount / this.stackSize);
final List<String> lines = new ArrayList<>();
lines.add("");
lines.add(Components.prepareLine(ShopConfig.AMOUNT_MENU_LORE_AMOUNT.toStr(),
"%amount%", String.valueOf(this.amount)));
if (stacks > 1) {
lines.add(Components.prepareLine(ShopConfig.AMOUNT_MENU_LORE_STACKS.toStr(),
"%stacks%", String.valueOf(stacks)));
}
lines.add(Components.prepareLine(ShopConfig.AMOUNT_MENU_LORE_PRICE.toStr(),
"%price%", this.formatPriceForMode(this.amount)));
final ItemStack result = ItemStackBuilder.of(item).lore(lines).build();
item.setItemMeta(result.getItemMeta());
}
private void confirm(@NotNull Player player) {
if (this.selling) {
this.plugin.scheduler().now(player, () ->
this.transactionManager.handleSell(player, this.shopItem, this.amount));
} else {
this.plugin.scheduler().now(player, () ->
this.transactionManager.handleBuy(player, this.shopItem, this.amount));
}
openWithAmount(player, this.amount);
}
private void openWithAmount(@NotNull Player player, int newAmount) {
this.plugin.scheduler().now(player, () -> {
new ShopAmountMenu(this.category, this.shopLoader,
this.transactionManager, this.shopItem, newAmount, this.selling)
.open(player);
MenuUtil.releaseClick(player.getUniqueId());
});
}
@NotNull
private static String formatItemName(@NotNull ItemStack item) {
if (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) {
final Component displayName = item.getItemMeta().displayName();
if (displayName != null) {
return PlainTextComponentSerializer.plainText().serialize(displayName);
}
}
final String[] parts = item.getType().name().toLowerCase().split("_");
final StringBuilder name = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
if (i > 0) {
name.append(" ");
}
name.append(Character.toUpperCase(parts[i].charAt(0)));
name.append(parts[i].substring(1));
}
return name.toString();
}
} |
|
Please send a small, standalone, code snippet that I can run alongside steps to reproduce the exception. I can't run the code above as it depends on other classes. |
Once I’m properly available again this week I’ll provide you specific details! |
I ran into an obscure issue that I "best resolved" by setting these types to "none" when upgrading from Minecraft 26.1 -> 26.2 (or from InvUI 2.1.1 -> 2.2.0).
Example warning: https://mclo.gs/ebZ1Oke (I confirmed this patch DOES minimize the trip).
The action processed correctly (changing an item stack with a customary ShopGUIPlus-like module via adding or removing items). I can't see specific use cases that should be natively defined; thus, I went ahead and proceeded down this route. Let me know if this is wrong or if you'd like this done differently!