[CommonItemStack] Add mimicAsType API

Signed-off-by: Irmo van den Berge <irmo.vandenberge@ziggo.nl>
This commit is contained in:
Irmo van den Berge committed 2025-05-15 18:17:28 +02:00
1 parent c8ac83787a
commit c327d6f72b
6 files changed
+162 -18

No files matched your search

@@ -63,7 +63,9 @@ public abstract class ItemStackHandle extends Template.Handle {
public abstract void setFireworksFlightDuration(int duration);
public abstract GameProfileHandle getSkullProfile();
public abstract void setSkullProfile(GameProfileHandle profile);
public abstract boolean hasItemModelSet();
public abstract MinecraftKeyHandle getItemModel();
public abstract MinecraftKeyHandle getItemModelIfSet();
public abstract void setItemModel(MinecraftKeyHandle key);
public abstract boolean hasCustomModelData();
public abstract boolean hasCustomModelDataValue();
@@ -143,7 +145,9 @@ public abstract class ItemStackHandle extends Template.Handle {
public final Template.Method<Void> setFireworksFlightDuration = new Template.Method<Void>();
public final Template.Method.Converted<GameProfileHandle> getSkullProfile = new Template.Method.Converted<GameProfileHandle>();
public final Template.Method.Converted<Void> setSkullProfile = new Template.Method.Converted<Void>();
public final Template.Method<Boolean> hasItemModelSet = new Template.Method<Boolean>();
public final Template.Method.Converted<MinecraftKeyHandle> getItemModel = new Template.Method.Converted<MinecraftKeyHandle>();
public final Template.Method.Converted<MinecraftKeyHandle> getItemModelIfSet = new Template.Method.Converted<MinecraftKeyHandle>();
public final Template.Method.Converted<Void> setItemModel = new Template.Method.Converted<Void>();
public final Template.Method<Boolean> hasCustomModelData = new Template.Method<Boolean>();
public final Template.Method<Boolean> hasCustomModelDataValue = new Template.Method<Boolean>();
@@ -69,7 +69,8 @@ public class Common {
"Common:NBTUtil:LoadSaveEquipment",
"Common:NBT:ChatTextSerialization",
"Module:RegionFlagTracker",
"Common:CommonItemStack:ItemModel"
"Common:CommonItemStack:ItemModel",
"Common:CommonItemStack:MimicAsType"
).collect(Collectors.toSet());
/**
@@ -1,5 +1,6 @@
package com.bergerkiller.bukkit.common.inventory;
import com.bergerkiller.bukkit.common.internal.CommonCapabilities;
import com.bergerkiller.bukkit.common.internal.CommonNMS;
import com.bergerkiller.bukkit.common.map.util.ModelInfoLookup;
import com.bergerkiller.bukkit.common.nbt.CommonTagCompound;
@@ -716,20 +717,36 @@ public final class CommonItemStack implements Cloneable {
*/
public boolean hasItemModel() {
return getHandle(true)
.map(ItemStackHandle::getItemModel)
.isPresent();
.map(ItemStackHandle::hasItemModelSet)
.orElse(Boolean.FALSE);
}
/**
* Gets the item model set for this item. If none are set and it defaults
* to the vanilla item model, returns <i>null</i>
* to the vanilla item model, returns the name of this vanilla item model.
*
* @return set item model, or <i>null</i> if not set
* @see ModelInfoLookup#lookupVanillaItemModel(CommonItemStack)
*/
public MinecraftKeyHandle getItemModel() {
if (CommonCapabilities.HAS_ITEM_MODEL_COMPONENT) {
return getHandle(true)
.map(ItemStackHandle::getItemModel)
.orElseGet(() -> ModelInfoLookup.lookupVanillaItemModel(this));
} else {
return ModelInfoLookup.lookupVanillaItemModel(this);
}
}
/**
* Gets the item model set for this item. If none are set and it defaults
* to the vanilla item model, returns <i>empty</i>
*
* @return set item model, or <i>empty</i> if not set
*/
public Optional<MinecraftKeyHandle> getItemModelIfSet() {
return getHandle(true)
.map(ItemStackHandle::getItemModel)
.orElse(null);
.map(ItemStackHandle::getItemModelIfSet);
}
/**
@@ -762,6 +779,41 @@ public final class CommonItemStack implements Cloneable {
return this;
}
/**
* Sets the {@link #setItemModel(String) item model} of this item to the type currently set,
* and sets the type of this item to the type specified, if item models are supported. This
* makes this item have the game behavior of the input type while still looking like (mimicing)
* the original type.<br>
* <br>
* If the server version is older than 1.21.2 this method does nothing, as item models are not
* supported there.<br>
* <br>
* <b>Use: </b>Call this method when creating new "tool" items to make them behave like a different
* item than is displayed. This can for example prevent block items from triggering block placement
* by the player.
*
* @param type Material Type to set on this item, that the game will use the behavior of
* @return this CommonItemStack
*/
public CommonItemStack mimicAsType(Material type) {
if (CommonCapabilities.HAS_ITEM_MODEL_COMPONENT) {
MinecraftKeyHandle itemModel = this.getItemModel();
setType(type);
setItemModel(itemModel);
}
return this;
}
/**
* Gets whether this version of the server supports setting a custom item model override
* on items. If false, then {@link #setItemModel(MinecraftKeyHandle)} is a no-op.
*
* @return True if item models can be set on items
*/
public static boolean canSetItemModel() {
return CommonCapabilities.HAS_ITEM_MODEL_COMPONENT;
}
/**
* Gets whether this item stores a custom model data integer value, which controls
* client-side display of models. Since Minecraft 1.14.
@@ -31,6 +31,7 @@ import java.util.Optional;
* This is needed because Minecraft is really stupid sometimes
*/
public class ModelInfoLookup {
private static final MinecraftKeyHandle MISSING_MODEL_NAME = MinecraftKeyHandle.createNew("minecraft", "builtin/missing");
private static final Map<Material, List<ModelNameEntry>> byMaterial = new EnumMap<>(Material.class);
private static final Map<String, ModelNameEntry> byItemName = new HashMap<>();
private static final Map<String, ModelNameEntry> byBlockName = new HashMap<>();
@@ -227,28 +228,37 @@ public class ModelInfoLookup {
}
public static String lookupItem(CommonItemStack itemStack) {
// Check for an item-model data component override
{
MinecraftKeyHandle itemModel = itemStack.getItemModel();
if (itemModel != null) {
if (itemModel.getNamespace().equals("minecraft")) {
return itemModel.getName();
} else {
return itemModel.toString();
}
}
MinecraftKeyHandle itemModel = itemStack.getItemModel();
if (itemModel.getNamespace().equals("minecraft")) {
return itemModel.getName();
} else {
return itemModel.toString();
}
}
/**
* Looks up the item model name of an item, completely ignoring the item model
* data component. This is mostly for internal use.
*
* @param itemStack CommonItemStack
* @return Set item model name key
*/
public static MinecraftKeyHandle lookupVanillaItemModel(CommonItemStack itemStack) {
return lookupItemModelNameEntry(itemStack).getItemNameKey()
.orElse(MISSING_MODEL_NAME);
}
private static ModelNameEntry lookupItemModelNameEntry(CommonItemStack itemStack) {
List<ModelNameEntry> entries = byMaterial.get(itemStack.getType());
if (entries.size() > 1) {
// Got to match it to the item
for (ModelNameEntry entry : entries) {
if (entry.matchDynamicItem(itemStack)) {
return entry.getItemName().orElse("unknown");
return entry;
}
}
}
return entries.get(0).getItemName().orElse("unknown");
return entries.get(0);
}
/**
@@ -266,6 +276,11 @@ public class ModelInfoLookup {
return Optional.empty();
}
@Override
public Optional<MinecraftKeyHandle> getItemNameKey() {
return Optional.empty();
}
@Override
public Optional<String> getBlockName() {
return Optional.empty();
@@ -291,6 +306,13 @@ public class ModelInfoLookup {
*/
Optional<String> getItemName();
/**
* Gets the {@link #getItemName()} but as an encoded MinecraftKey
*
* @return Item model name as MinecraftKey
*/
Optional<MinecraftKeyHandle> getItemNameKey();
/**
* Returns the block name, if this entry has a block
*
@@ -347,6 +369,7 @@ public class ModelInfoLookup {
private final Material inkSacMaterial;
private final int damageValue;
private final String itemName;
private final MinecraftKeyHandle itemNameKey;
public static List<ModelNameEntry> createLegacyInkSacMaterials(Material inkSacItemMaterial) {
return Arrays.asList(
@@ -373,6 +396,7 @@ public class ModelInfoLookup {
this.inkSacMaterial = inkSacMaterial;
this.damageValue = damageValue;
this.itemName = "dye_" + colorName;
this.itemNameKey = MinecraftKeyHandle.createNew("minecraft", this.itemName);
}
@Override
@@ -385,6 +409,11 @@ public class ModelInfoLookup {
return Optional.of(itemName);
}
@Override
public Optional<MinecraftKeyHandle> getItemNameKey() {
return Optional.of(itemNameKey);
}
@Override
public Optional<String> getBlockName() {
return Optional.empty();
@@ -409,6 +438,7 @@ public class ModelInfoLookup {
private final Material fishItemMaterial;
private final int damageValue;
private final String itemName;
private final MinecraftKeyHandle itemNameKey;
public static List<ModelNameEntry> createLegacyFishMaterials(Material fishItemMaterial, boolean cooked) {
return Arrays.asList(
@@ -423,6 +453,7 @@ public class ModelInfoLookup {
this.fishItemMaterial = fishItemMaterial;
this.damageValue = damageValue;
this.itemName = (cooked ? "cooked_" : "") + fishName;
this.itemNameKey = MinecraftKeyHandle.createNew("minecraft", this.itemName);
}
@Override
@@ -435,6 +466,11 @@ public class ModelInfoLookup {
return Optional.of(itemName);
}
@Override
public Optional<MinecraftKeyHandle> getItemNameKey() {
return Optional.of(itemNameKey);
}
@Override
public Optional<String> getBlockName() {
return Optional.empty();
@@ -459,11 +495,13 @@ public class ModelInfoLookup {
private static class ItemModelNameEntry implements ModelNameEntry {
public final Material itemMaterial;
public final String itemName;
private final MinecraftKeyHandle itemNameKey;
// Note: only for non-block materials!
public ItemModelNameEntry(Material itemMaterial) {
this.itemMaterial = itemMaterial;
this.itemName = lookupItemResourceName(itemMaterial);
this.itemNameKey = MinecraftKeyHandle.createNew("minecraft", this.itemName);
}
@Override
@@ -476,6 +514,11 @@ public class ModelInfoLookup {
return Optional.of(itemName);
}
@Override
public Optional<MinecraftKeyHandle> getItemNameKey() {
return Optional.of(itemNameKey);
}
@Override
public Optional<String> getBlockName() {
return Optional.empty();
@@ -517,6 +560,7 @@ public class ModelInfoLookup {
public final BlockData blockData;
public final String blockName;
public final String itemName;
public final MinecraftKeyHandle itemNameKey;
public BlockDataModelNameEntry(BlockData blockData) {
this.blockData = blockData;
@@ -532,6 +576,7 @@ public class ModelInfoLookup {
} else {
itemName = blockName;
}
itemNameKey = MinecraftKeyHandle.createNew("minecraft", itemName);
}
@Override
@@ -544,6 +589,11 @@ public class ModelInfoLookup {
return Optional.of(itemName);
}
@Override
public Optional<MinecraftKeyHandle> getItemNameKey() {
return Optional.of(itemNameKey);
}
@Override
public Optional<String> getBlockName() {
return Optional.of(blockName);
@@ -793,10 +793,22 @@ class ItemStack {
}
#if version >= 1.21.2
public boolean hasItemModelSet() {
return instance.hasNonDefault(DataComponents.ITEM_MODEL);
}
public (MinecraftKeyHandle) MinecraftKey getItemModel() {
return (MinecraftKey) instance.get(DataComponents.ITEM_MODEL);
}
public (MinecraftKeyHandle) MinecraftKey getItemModelIfSet() {
if (instance.hasNonDefault(DataComponents.ITEM_MODEL)) {
return (MinecraftKey) instance.get(DataComponents.ITEM_MODEL);
} else {
return null;
}
}
public void setItemModel((MinecraftKeyHandle) MinecraftKey key) {
if (key != null) {
instance.set(DataComponents.ITEM_MODEL, key);
@@ -805,10 +817,18 @@ class ItemStack {
}
}
#else
public boolean hasItemModelSet() {
return false;
}
public (MinecraftKeyHandle) MinecraftKey getItemModel() {
return null;
}
public (MinecraftKeyHandle) MinecraftKey getItemModelIfSet() {
return null;
}
public void setItemModel((MinecraftKeyHandle) MinecraftKey key) {
}
#endif
@@ -1,5 +1,6 @@
package com.bergerkiller.bukkit.common;
import com.bergerkiller.bukkit.common.inventory.CommonItemMaterials;
import com.bergerkiller.bukkit.common.inventory.CommonItemStack;
import com.bergerkiller.bukkit.common.inventory.InventoryBaseImpl;
import com.bergerkiller.bukkit.common.nbt.CommonTagCompound;
@@ -19,6 +20,22 @@ import static org.junit.Assert.*;
public class CommonItemStackTest {
@Test
public void testMimicAsType() {
if (!CommonItemStack.canSetItemModel()) {
return;
}
CommonItemStack item = CommonItemStack.create(MaterialUtil.getFirst("DIAMOND_SWORD", "LEGACY_DIAMOND_SWORD"), 1);
System.out.println(item.getItemModel());
assertFalse(item.hasItemModel());
item.mimicAsType(CommonItemMaterials.STICK); // Hits like a wet noodle
assertEquals(CommonItemMaterials.STICK, item.getType());
assertEquals("minecraft:diamond_sword", item.getItemModel().toString());
}
@Test
public void testVanillaDisplayName() {
// Note: display name is a translatable, which we decode into English.