542 lines
17 KiB
Java
542 lines
17 KiB
Java
package net.minecraft.world.item;
|
|
|
|
import com.google.common.collect.Interner;
|
|
import com.google.common.collect.Interners;
|
|
import com.google.common.collect.Maps;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.HolderLookup;
|
|
import net.minecraft.core.component.DataComponentMap;
|
|
import net.minecraft.core.component.DataComponentType;
|
|
import net.minecraft.core.component.DataComponents;
|
|
import net.minecraft.core.registries.BuiltInRegistries;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.Unit;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.InteractionResult;
|
|
import net.minecraft.world.InteractionResultHolder;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EquipmentSlot;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.SlotAccess;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.flag.FeatureElement;
|
|
import net.minecraft.world.flag.FeatureFlag;
|
|
import net.minecraft.world.flag.FeatureFlagSet;
|
|
import net.minecraft.world.flag.FeatureFlags;
|
|
import net.minecraft.world.food.FoodProperties;
|
|
import net.minecraft.world.inventory.ClickAction;
|
|
import net.minecraft.world.inventory.Slot;
|
|
import net.minecraft.world.inventory.tooltip.TooltipComponent;
|
|
import net.minecraft.world.item.component.ItemAttributeModifiers;
|
|
import net.minecraft.world.item.component.Tool;
|
|
import net.minecraft.world.item.context.UseOnContext;
|
|
import net.minecraft.world.level.ClipContext;
|
|
import net.minecraft.world.level.ItemLike;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.saveddata.maps.MapId;
|
|
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
|
|
import net.minecraft.world.phys.BlockHitResult;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class Item implements FeatureElement, ItemLike {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
public static final Map<Block, Item> BY_BLOCK = Maps.<Block, Item>newHashMap();
|
|
public static final ResourceLocation BASE_ATTACK_DAMAGE_ID = ResourceLocation.withDefaultNamespace("base_attack_damage");
|
|
public static final ResourceLocation BASE_ATTACK_SPEED_ID = ResourceLocation.withDefaultNamespace("base_attack_speed");
|
|
public static final int DEFAULT_MAX_STACK_SIZE = 64;
|
|
public static final int ABSOLUTE_MAX_STACK_SIZE = 99;
|
|
public static final int MAX_BAR_WIDTH = 13;
|
|
private final Holder.Reference<Item> builtInRegistryHolder = BuiltInRegistries.ITEM.createIntrusiveHolder(this);
|
|
private final DataComponentMap components;
|
|
@Nullable
|
|
private final Item craftingRemainingItem;
|
|
@Nullable
|
|
private String descriptionId;
|
|
private final FeatureFlagSet requiredFeatures;
|
|
|
|
public static int getId(Item item) {
|
|
return item == null ? 0 : BuiltInRegistries.ITEM.getId(item);
|
|
}
|
|
|
|
public static Item byId(int id) {
|
|
return BuiltInRegistries.ITEM.byId(id);
|
|
}
|
|
|
|
@Deprecated
|
|
public static Item byBlock(Block block) {
|
|
return (Item)BY_BLOCK.getOrDefault(block, Items.AIR);
|
|
}
|
|
|
|
public Item(Item.Properties properties) {
|
|
this.components = properties.buildAndValidateComponents();
|
|
this.craftingRemainingItem = properties.craftingRemainingItem;
|
|
this.requiredFeatures = properties.requiredFeatures;
|
|
if (SharedConstants.IS_RUNNING_IN_IDE) {
|
|
String string = this.getClass().getSimpleName();
|
|
if (!string.endsWith("Item")) {
|
|
LOGGER.error("Item classes should end with Item and {} doesn't.", string);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
public Holder.Reference<Item> builtInRegistryHolder() {
|
|
return this.builtInRegistryHolder;
|
|
}
|
|
|
|
public DataComponentMap components() {
|
|
return this.components;
|
|
}
|
|
|
|
public int getDefaultMaxStackSize() {
|
|
return this.components.getOrDefault(DataComponents.MAX_STACK_SIZE, 1);
|
|
}
|
|
|
|
/**
|
|
* Called as the item is being used by an entity.
|
|
*/
|
|
public void onUseTick(Level level, LivingEntity livingEntity, ItemStack stack, int remainingUseDuration) {
|
|
}
|
|
|
|
public void onDestroyed(ItemEntity itemEntity) {
|
|
}
|
|
|
|
public void verifyComponentsAfterLoad(ItemStack stack) {
|
|
}
|
|
|
|
public boolean canAttackBlock(BlockState state, Level level, BlockPos pos, Player player) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public Item asItem() {
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Called when this item is used when targeting a Block
|
|
*/
|
|
public InteractionResult useOn(UseOnContext context) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
public float getDestroySpeed(ItemStack stack, BlockState state) {
|
|
Tool tool = stack.get(DataComponents.TOOL);
|
|
return tool != null ? tool.getMiningSpeed(state) : 1.0F;
|
|
}
|
|
|
|
/**
|
|
* Called to trigger the item's "innate" right click behavior. To handle when this item is used on a Block, see {@link net.minecraft.world.item.Item#useOn(net.minecraft.world.item.context.UseOnContext)}.
|
|
*/
|
|
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand usedHand) {
|
|
ItemStack itemStack = player.getItemInHand(usedHand);
|
|
FoodProperties foodProperties = itemStack.get(DataComponents.FOOD);
|
|
if (foodProperties != null) {
|
|
if (player.canEat(foodProperties.canAlwaysEat())) {
|
|
player.startUsingItem(usedHand);
|
|
return InteractionResultHolder.consume(itemStack);
|
|
} else {
|
|
return InteractionResultHolder.fail(itemStack);
|
|
}
|
|
} else {
|
|
return InteractionResultHolder.pass(player.getItemInHand(usedHand));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the player finishes using this Item (E.g. finishes eating.). Not called when the player stops using the Item before the action is complete.
|
|
*/
|
|
public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity livingEntity) {
|
|
FoodProperties foodProperties = stack.get(DataComponents.FOOD);
|
|
return foodProperties != null ? livingEntity.eat(level, stack, foodProperties) : stack;
|
|
}
|
|
|
|
public boolean isBarVisible(ItemStack stack) {
|
|
return stack.isDamaged();
|
|
}
|
|
|
|
public int getBarWidth(ItemStack stack) {
|
|
return Mth.clamp(Math.round(13.0F - stack.getDamageValue() * 13.0F / stack.getMaxDamage()), 0, 13);
|
|
}
|
|
|
|
public int getBarColor(ItemStack stack) {
|
|
int i = stack.getMaxDamage();
|
|
float f = Math.max(0.0F, ((float)i - stack.getDamageValue()) / i);
|
|
return Mth.hsvToRgb(f / 3.0F, 1.0F, 1.0F);
|
|
}
|
|
|
|
public boolean overrideStackedOnOther(ItemStack stack, Slot slot, ClickAction action, Player player) {
|
|
return false;
|
|
}
|
|
|
|
public boolean overrideOtherStackedOnMe(ItemStack stack, ItemStack other, Slot slot, ClickAction action, Player player, SlotAccess access) {
|
|
return false;
|
|
}
|
|
|
|
public float getAttackDamageBonus(Entity target, float damage, DamageSource damageSource) {
|
|
return 0.0F;
|
|
}
|
|
|
|
/**
|
|
* Current implementations of this method in child classes do not use the entry argument beside ev. They just raise the damage on the stack.
|
|
*/
|
|
public boolean hurtEnemy(ItemStack stack, LivingEntity target, LivingEntity attacker) {
|
|
return false;
|
|
}
|
|
|
|
public void postHurtEnemy(ItemStack stack, LivingEntity target, LivingEntity attacker) {
|
|
}
|
|
|
|
/**
|
|
* Called when a {@link net.minecraft.world.level.block.Block} is destroyed using this Item. Return {@code true} to trigger the "Use Item" statistic.
|
|
*/
|
|
public boolean mineBlock(ItemStack stack, Level level, BlockState state, BlockPos pos, LivingEntity miningEntity) {
|
|
Tool tool = stack.get(DataComponents.TOOL);
|
|
if (tool == null) {
|
|
return false;
|
|
} else {
|
|
if (!level.isClientSide && state.getDestroySpeed(level, pos) != 0.0F && tool.damagePerBlock() > 0) {
|
|
stack.hurtAndBreak(tool.damagePerBlock(), miningEntity, EquipmentSlot.MAINHAND);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public boolean isCorrectToolForDrops(ItemStack stack, BlockState state) {
|
|
Tool tool = stack.get(DataComponents.TOOL);
|
|
return tool != null && tool.isCorrectForDrops(state);
|
|
}
|
|
|
|
/**
|
|
* Try interacting with given entity. Return {@code InteractionResult.PASS} if nothing should happen.
|
|
*/
|
|
public InteractionResult interactLivingEntity(ItemStack stack, Player player, LivingEntity interactionTarget, InteractionHand usedHand) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
public Component getDescription() {
|
|
return Component.translatable(this.getDescriptionId());
|
|
}
|
|
|
|
public String toString() {
|
|
return BuiltInRegistries.ITEM.wrapAsHolder(this).getRegisteredName();
|
|
}
|
|
|
|
protected String getOrCreateDescriptionId() {
|
|
if (this.descriptionId == null) {
|
|
this.descriptionId = Util.makeDescriptionId("item", BuiltInRegistries.ITEM.getKey(this));
|
|
}
|
|
|
|
return this.descriptionId;
|
|
}
|
|
|
|
/**
|
|
* Returns the unlocalized name of this item.
|
|
*/
|
|
public String getDescriptionId() {
|
|
return this.getOrCreateDescriptionId();
|
|
}
|
|
|
|
/**
|
|
* Returns the unlocalized name of this item. This version accepts an ItemStack so different stacks can have different names based on their damage or NBT.
|
|
*/
|
|
public String getDescriptionId(ItemStack stack) {
|
|
return this.getDescriptionId();
|
|
}
|
|
|
|
@Nullable
|
|
public final Item getCraftingRemainingItem() {
|
|
return this.craftingRemainingItem;
|
|
}
|
|
|
|
/**
|
|
* True if this Item has a container item (a.k.a. crafting result)
|
|
*/
|
|
public boolean hasCraftingRemainingItem() {
|
|
return this.craftingRemainingItem != null;
|
|
}
|
|
|
|
/**
|
|
* Called each tick as long the item is in a player's inventory. Used by maps to check if it's in a player's hand and update its contents.
|
|
*/
|
|
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slotId, boolean isSelected) {
|
|
}
|
|
|
|
/**
|
|
* Called when item is crafted/smelted. Used only by maps so far.
|
|
*/
|
|
public void onCraftedBy(ItemStack stack, Level level, Player player) {
|
|
this.onCraftedPostProcess(stack, level);
|
|
}
|
|
|
|
public void onCraftedPostProcess(ItemStack stack, Level level) {
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if this is a complex item.
|
|
*/
|
|
public boolean isComplex() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the action that specifies what animation to play when the item is being used.
|
|
*/
|
|
public UseAnim getUseAnimation(ItemStack stack) {
|
|
return stack.has(DataComponents.FOOD) ? UseAnim.EAT : UseAnim.NONE;
|
|
}
|
|
|
|
public int getUseDuration(ItemStack stack, LivingEntity entity) {
|
|
FoodProperties foodProperties = stack.get(DataComponents.FOOD);
|
|
return foodProperties != null ? foodProperties.eatDurationTicks() : 0;
|
|
}
|
|
|
|
/**
|
|
* Called when the player stops using an Item (stops holding the right mouse button).
|
|
*/
|
|
public void releaseUsing(ItemStack stack, Level level, LivingEntity livingEntity, int timeCharged) {
|
|
}
|
|
|
|
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
|
|
}
|
|
|
|
public Optional<TooltipComponent> getTooltipImage(ItemStack stack) {
|
|
return Optional.empty();
|
|
}
|
|
|
|
public Component getName(ItemStack stack) {
|
|
return Component.translatable(this.getDescriptionId(stack));
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if this item has an enchantment glint. By default, this returns {@code stack.isEnchanted()}, but other items can override it (for instance, written books always return true).
|
|
*
|
|
* Note that if you override this method, you generally want to also call the super version (on {@link Item}) to get the glint for enchanted items. Of course, that is unnecessary if the overwritten version always returns true.
|
|
*/
|
|
public boolean isFoil(ItemStack stack) {
|
|
return stack.isEnchanted();
|
|
}
|
|
|
|
/**
|
|
* Checks isDamagable and if it cannot be stacked
|
|
*/
|
|
public boolean isEnchantable(ItemStack stack) {
|
|
return stack.getMaxStackSize() == 1 && stack.has(DataComponents.MAX_DAMAGE);
|
|
}
|
|
|
|
protected static BlockHitResult getPlayerPOVHitResult(Level level, Player player, ClipContext.Fluid fluidMode) {
|
|
Vec3 vec3 = player.getEyePosition();
|
|
Vec3 vec32 = vec3.add(player.calculateViewVector(player.getXRot(), player.getYRot()).scale(player.blockInteractionRange()));
|
|
return level.clip(new ClipContext(vec3, vec32, ClipContext.Block.OUTLINE, fluidMode, player));
|
|
}
|
|
|
|
/**
|
|
* Return the enchantability factor of the item, most of the time is based on material.
|
|
*/
|
|
public int getEnchantmentValue() {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Return whether this item is repairable in an anvil.
|
|
*/
|
|
public boolean isValidRepairItem(ItemStack stack, ItemStack repairCandidate) {
|
|
return false;
|
|
}
|
|
|
|
@Deprecated
|
|
public ItemAttributeModifiers getDefaultAttributeModifiers() {
|
|
return ItemAttributeModifiers.EMPTY;
|
|
}
|
|
|
|
/**
|
|
* If this stack's item is a crossbow
|
|
*/
|
|
public boolean useOnRelease(ItemStack stack) {
|
|
return false;
|
|
}
|
|
|
|
public ItemStack getDefaultInstance() {
|
|
return new ItemStack(this);
|
|
}
|
|
|
|
public SoundEvent getDrinkingSound() {
|
|
return SoundEvents.GENERIC_DRINK;
|
|
}
|
|
|
|
public SoundEvent getEatingSound() {
|
|
return SoundEvents.GENERIC_EAT;
|
|
}
|
|
|
|
public SoundEvent getBreakingSound() {
|
|
return SoundEvents.ITEM_BREAK;
|
|
}
|
|
|
|
public boolean canFitInsideContainerItems() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public FeatureFlagSet requiredFeatures() {
|
|
return this.requiredFeatures;
|
|
}
|
|
|
|
public static class Properties {
|
|
private static final Interner<DataComponentMap> COMPONENT_INTERNER = Interners.newStrongInterner();
|
|
@Nullable
|
|
private DataComponentMap.Builder components;
|
|
@Nullable
|
|
Item craftingRemainingItem;
|
|
FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET;
|
|
|
|
public Item.Properties food(FoodProperties food) {
|
|
return this.component(DataComponents.FOOD, food);
|
|
}
|
|
|
|
public Item.Properties stacksTo(int maxStackSize) {
|
|
return this.component(DataComponents.MAX_STACK_SIZE, maxStackSize);
|
|
}
|
|
|
|
public Item.Properties durability(int maxDamage) {
|
|
this.component(DataComponents.MAX_DAMAGE, maxDamage);
|
|
this.component(DataComponents.MAX_STACK_SIZE, 1);
|
|
this.component(DataComponents.DAMAGE, 0);
|
|
return this;
|
|
}
|
|
|
|
public Item.Properties craftRemainder(Item craftingRemainingItem) {
|
|
this.craftingRemainingItem = craftingRemainingItem;
|
|
return this;
|
|
}
|
|
|
|
public Item.Properties rarity(Rarity rarity) {
|
|
return this.component(DataComponents.RARITY, rarity);
|
|
}
|
|
|
|
public Item.Properties fireResistant() {
|
|
return this.component(DataComponents.FIRE_RESISTANT, Unit.INSTANCE);
|
|
}
|
|
|
|
public Item.Properties jukeboxPlayable(ResourceKey<JukeboxSong> song) {
|
|
return this.component(DataComponents.JUKEBOX_PLAYABLE, new JukeboxPlayable(new EitherHolder<>(song), true));
|
|
}
|
|
|
|
public Item.Properties requiredFeatures(FeatureFlag... requiredFeatures) {
|
|
this.requiredFeatures = FeatureFlags.REGISTRY.subset(requiredFeatures);
|
|
return this;
|
|
}
|
|
|
|
public <T> Item.Properties component(DataComponentType<T> component, T value) {
|
|
if (this.components == null) {
|
|
this.components = DataComponentMap.builder().addAll(DataComponents.COMMON_ITEM_COMPONENTS);
|
|
}
|
|
|
|
this.components.set(component, value);
|
|
return this;
|
|
}
|
|
|
|
public Item.Properties attributes(ItemAttributeModifiers attributes) {
|
|
return this.component(DataComponents.ATTRIBUTE_MODIFIERS, attributes);
|
|
}
|
|
|
|
DataComponentMap buildAndValidateComponents() {
|
|
DataComponentMap dataComponentMap = this.buildComponents();
|
|
if (dataComponentMap.has(DataComponents.DAMAGE) && dataComponentMap.getOrDefault(DataComponents.MAX_STACK_SIZE, 1) > 1) {
|
|
throw new IllegalStateException("Item cannot have both durability and be stackable");
|
|
} else {
|
|
return dataComponentMap;
|
|
}
|
|
}
|
|
|
|
private DataComponentMap buildComponents() {
|
|
return this.components == null ? DataComponents.COMMON_ITEM_COMPONENTS : COMPONENT_INTERNER.intern(this.components.build());
|
|
}
|
|
}
|
|
|
|
public interface TooltipContext {
|
|
Item.TooltipContext EMPTY = new Item.TooltipContext() {
|
|
@Nullable
|
|
@Override
|
|
public HolderLookup.Provider registries() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public float tickRate() {
|
|
return 20.0F;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public MapItemSavedData mapData(MapId mapId) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
@Nullable
|
|
HolderLookup.Provider registries();
|
|
|
|
float tickRate();
|
|
|
|
@Nullable
|
|
MapItemSavedData mapData(MapId mapId);
|
|
|
|
static Item.TooltipContext of(@Nullable Level level) {
|
|
return level == null ? EMPTY : new Item.TooltipContext() {
|
|
@Override
|
|
public HolderLookup.Provider registries() {
|
|
return level.registryAccess();
|
|
}
|
|
|
|
@Override
|
|
public float tickRate() {
|
|
return level.tickRateManager().tickrate();
|
|
}
|
|
|
|
@Override
|
|
public MapItemSavedData mapData(MapId mapId) {
|
|
return level.getMapData(mapId);
|
|
}
|
|
};
|
|
}
|
|
|
|
static Item.TooltipContext of(HolderLookup.Provider registries) {
|
|
return new Item.TooltipContext() {
|
|
@Override
|
|
public HolderLookup.Provider registries() {
|
|
return registries;
|
|
}
|
|
|
|
@Override
|
|
public float tickRate() {
|
|
return 20.0F;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public MapItemSavedData mapData(MapId mapId) {
|
|
return null;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|