Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,50 @@ current Paper API surface.
[`PR_DESCRIPTION.md`](PR_DESCRIPTION.md) for the 1.18.0 profile-backed
numbers).

## 🧰 Item properties

Item properties are configured per menu item. The `tooltipDisplay` property
wraps the Paper `tooltip_display` data component (Minecraft 1.21.5+).

Hide an item's tooltip entirely:

```hocon
material: DIAMOND_SWORD
tooltipDisplay {
hideTooltip: true
}
```

Hide the tooltip of specific components, e.g. only the Bundle contents:

```hocon
material: BUNDLE
tooltipDisplay {
hiddenComponents: [
BUNDLE_CONTENTS
]
}
```

Several components at once:

```hocon
tooltipDisplay {
hiddenComponents: [
BUNDLE_CONTENTS,
ATTRIBUTE_MODIFIERS,
ENCHANTMENTS
]
}
```

`hiddenComponents` accepts component keys case-insensitively and in
namespaced form (`BUNDLE_CONTENTS`, `bundle_contents`,
`"minecraft:bundle_contents"`). Unknown component names fail loudly at menu load.
Namespaced keys contain `:`, which HOCON only allows inside quotes, so write
them as `"minecraft:bundle_contents"`. Namespaced keys without an explicit
namespace are assumed to be `minecraft:`.

## 🔗 Links
- [SpigotMC](https://www.spigotmc.org/resources/abstract-menus-an-advanced-gui-plugin.75107/)
- [Discord](https://discord.gg/kt4P9Cgw)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import ru.abstractmenus.data.properties.PropBindings;
import ru.abstractmenus.data.properties.PropNameLight;
import ru.abstractmenus.data.properties.PropLoreLight;
import ru.abstractmenus.data.properties.PropTooltipDisplay;

/**
* Core item property registrations. Mirrors {@code ItemProps.init()} before
Expand All @@ -60,6 +61,7 @@ void register(AbstractMenusApi api, MenuExtension owner) {
api.itemProperties().register("enchantments", PropEnchantments.class, new PropEnchantments.Serializer(), owner);
api.itemProperties().register("color", PropColor.class, new PropColor.Serializer(), owner);
api.itemProperties().register("flags", PropFlags.class, new PropFlags.Serializer(), owner);
api.itemProperties().register("tooltipDisplay", PropTooltipDisplay.class, new PropTooltipDisplay.Serializer(), owner);
api.itemProperties().register("unbreakable", PropUnbreakable.class, new PropUnbreakable.Serializer(), owner);
api.itemProperties().register("potionData", PropPotionData.class, new PropPotionData.Serializer(), owner);
api.itemProperties().register("fireworkData", PropFireworkData.class, new PropFireworkData.Serializer(), owner);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package ru.abstractmenus.data.properties;

import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.TooltipDisplay;
import org.bukkit.Registry;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import ru.abstractmenus.api.inventory.ItemProperty;
import ru.abstractmenus.api.inventory.Menu;
import ru.abstractmenus.datatype.TypeBool;
import ru.abstractmenus.hocon.api.ConfigNode;
import ru.abstractmenus.hocon.api.serialize.NodeSerializeException;
import ru.abstractmenus.hocon.api.serialize.NodeSerializer;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
* Item property controlling the {@code tooltip_display} data component
* ({@link TooltipDisplay}). Hides a tooltip entirely via {@code hideTooltip}
* or hides the tooltip of specific components via {@code hiddenComponents}.
*
* <pre>{@code
* tooltipDisplay {
* hideTooltip: false
* hiddenComponents: [BUNDLE_CONTENTS, ENCHANTMENTS]
* }
* }</pre>
*/
public class PropTooltipDisplay implements ItemProperty {

private final TypeBool hideTooltip;
private final Set<DataComponentType> hiddenComponents;

private PropTooltipDisplay(TypeBool hideTooltip, Set<DataComponentType> hiddenComponents) {
this.hideTooltip = hideTooltip;
this.hiddenComponents = hiddenComponents;
}

@Override
public boolean canReplaceMaterial() {
return false;
}

@Override
public boolean isApplyMeta() {
return true;
}

@Override
public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) {
if (hideTooltip == null && hiddenComponents == null) {
return;
}

tooltipDisplay(itemStack, player, menu);
}

private void tooltipDisplay(ItemStack itemStack, Player player, Menu menu) {
TooltipDisplay existing = itemStack.getData(DataComponentTypes.TOOLTIP_DISPLAY);

boolean hide = (hideTooltip != null)
? hideTooltip.getBool(player, menu)
: (existing != null && existing.hideTooltip());

Set<DataComponentType> hidden = (hiddenComponents != null)
? hiddenComponents
: (existing != null ? existing.hiddenComponents() : Collections.emptySet());

itemStack.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay()
.hideTooltip(hide)
.hiddenComponents(hidden)
.build());
}

public static class Serializer implements NodeSerializer<PropTooltipDisplay> {

@Override
public PropTooltipDisplay deserialize(Class type, ConfigNode node) throws NodeSerializeException {
TypeBool hideTooltip = null;
Set<DataComponentType> hidden = null;

ConfigNode hideNode = node.node("hideTooltip");
if (hideNode.rawValue() != null) {
hideTooltip = new TypeBool.Serializer().deserialize(TypeBool.class, hideNode);
}

ConfigNode hiddenNode = node.node("hiddenComponents");
if (hiddenNode.rawValue() != null) {
if (!hiddenNode.isList()) {
throw new NodeSerializeException(node, "Field 'hiddenComponents' must be a list");
}
hidden = new LinkedHashSet<>();
for (ConfigNode componentNode : hiddenNode.childrenList()) {
String raw = componentNode.getString();
DataComponentType component = resolveComponent(raw);
if (component == null) {
throw new NodeSerializeException(componentNode, "Unknown data component '" + raw + "'");
}
hidden.add(component);
}
}

return new PropTooltipDisplay(hideTooltip, hidden);
}

}

/**
* Test hook: replaces the live {@link Registry#DATA_COMPONENT_TYPE} source.
* Accepts any {@link Iterable} so tests do not need to mock the Registry
* class itself (its static initializer requires a live Paper server).
*/
static void configureRegistry(Iterable<DataComponentType> registry) {
registryOverride = registry;
cache = null;
}

/**
* Test hook: restores the live registry source and clears the cache.
*/
static void resetRegistry() {
registryOverride = null;
cache = null;
}

static DataComponentType resolveComponent(String raw) {
if (raw == null) {
return null;
}
String key = raw.trim().toLowerCase(Locale.ROOT);
int colon = key.indexOf(':');
if (colon >= 0) {
key = key.substring(colon + 1);
}
if (key.isEmpty()) {
return null;
}
return registryMap().get(key);
}

private static volatile Iterable<DataComponentType> registryOverride;
private static volatile Map<String, DataComponentType> cache;

private static Map<String, DataComponentType> registryMap() {
Map<String, DataComponentType> local = cache;
if (local != null) {
return local;
}
Iterable<DataComponentType> source = registryOverride;
if (source == null) {
try {
source = Registry.DATA_COMPONENT_TYPE;
} catch (Throwable t) {
source = null;
}
}
Map<String, DataComponentType> built = new HashMap<>();
if (source != null) {
for (DataComponentType type : source) {
if (type.getKey() == null) {
continue;
}
built.put(type.getKey().getKey().toLowerCase(Locale.ROOT), type);
}
}
cache = built;
return built;
}
}
10 changes: 10 additions & 0 deletions plugin/src/main/resources/menu.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ items: [
closeMenu: true
}
},
{
slot: 5
material: BUNDLE
name: "&6&lBundle"
tooltipDisplay {
hiddenComponents: [
BUNDLE_CONTENTS
]
}
},
{
slot: 8
material: BARRIER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ void coreExtension_registersExpectedRules() {
@Test
void coreExtension_registersExpectedItemProperties() {
new CoreExtension().onEnable(api);
assertEquals(32, api.itemProperties().keys().size(),
"expected 32 core item-property types (matches Task 10 migration count)");
assertEquals(33, api.itemProperties().keys().size(),
"expected 33 core item-property types (matches Task 10 migration count)");
}

@Test
Expand Down Expand Up @@ -128,6 +128,7 @@ void coreExtension_canonicalRules_resolve() {
void coreExtension_canonicalItemProperty_resolve() {
new CoreExtension().onEnable(api);
assertNotNull(api.itemProperties().get("material"), "material item-property must be registered");
assertNotNull(api.itemProperties().get("tooltipDisplay"), "tooltipDisplay item-property must be registered");
}

// -------------------------------------------------------------------------
Expand Down
Loading