/* * This file is part of Applied Energistics 2. * Copyright (c) 2013 - 2020, AlgorithmX2, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Applied Energistics 2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Applied Energistics 2. If not, see . */ package appeng.util.item; import java.util.List; import java.util.Objects; import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.common.primitives.Ints; import gregtech.api.items.IToolItem; import ic2.api.item.ICustomDamageItem; import io.netty.buffer.ByteBuf; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.fml.common.network.ByteBufUtils; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.items.ItemHandlerHelper; import appeng.api.config.FuzzyMode; import appeng.api.storage.IStorageChannel; import appeng.api.storage.channels.IItemStorageChannel; import appeng.api.storage.data.IAEItemStack; import appeng.core.Api; import appeng.util.Platform; public class AEItemStack extends AEStack implements IAEItemStack { private static final String NBT_STACKSIZE = "Cnt"; private static final String NBT_REQUESTABLE = "Req"; private static final String NBT_CRAFTABLE = "Craft"; private final AESharedItemStack sharedStack; private Optional oreReference; @SideOnly( Side.CLIENT ) private String displayName; @SideOnly( Side.CLIENT ) private List tooltip; private ItemStack cachedItemStack; private AEItemStack( final AEItemStack is ) { this.setStackSize( is.getStackSize() ); this.setCraftable( is.isCraftable() ); this.setCountRequestable( is.getCountRequestable() ); this.sharedStack = is.sharedStack; this.oreReference = is.oreReference; this.cachedItemStack = is.cachedItemStack; } private AEItemStack( final AESharedItemStack is, long size ) { this.sharedStack = is; this.setStackSize( size ); this.setCraftable( false ); this.setCountRequestable( 0 ); this.oreReference = OreHelper.INSTANCE.getOre( is.getDefinition() ); } @Nullable public static AEItemStack fromItemStack( @Nonnull final ItemStack stack ) { if( stack.isEmpty() ) { return null; } return new AEItemStack( AEItemStackRegistry.getRegisteredStack( stack ), stack.getCount() ); } public static IAEItemStack fromNBT( final NBTTagCompound i ) { if( i == null ) { return null; } final ItemStack itemstack = new ItemStack( i ); if( itemstack.isEmpty() ) { return null; } final AEItemStack item = AEItemStack.fromItemStack( itemstack ); item.setStackSize( i.getLong( NBT_STACKSIZE ) ); item.setCountRequestable( i.getLong( NBT_REQUESTABLE ) ); item.setCraftable( i.getBoolean( NBT_CRAFTABLE ) ); return item; } @Override public void writeToNBT( final NBTTagCompound i ) { this.getDefinition().writeToNBT( i ); i.setLong( NBT_STACKSIZE, this.getStackSize() ); i.setLong( NBT_REQUESTABLE, this.getCountRequestable() ); i.setBoolean( NBT_CRAFTABLE, this.isCraftable() ); } public static AEItemStack fromPacket( final ByteBuf data ) { final byte mask = data.readByte(); final byte stackType = (byte) ( ( mask & 0x0C ) >> 2 ); final byte countReqType = (byte) ( ( mask & 0x30 ) >> 4 ); final boolean isCraftable = ( mask & 0x40 ) > 0; final ItemStack itemstack = new ItemStack( ByteBufUtils.readTag( data ) ); final long stackSize = getPacketValue( stackType, data ); final long countRequestable = getPacketValue( countReqType, data ); if( itemstack.isEmpty() ) { return null; } final AEItemStack item = new AEItemStack( AEItemStackRegistry.getRegisteredStack( itemstack ), stackSize ); item.setCountRequestable( countRequestable ); item.setCraftable( isCraftable ); return item; } @Override public void writeToPacket( final ByteBuf i ) { final byte mask = (byte) ( ( this.getType( this.getStackSize() ) << 2 ) | ( this.getType( this.getCountRequestable() ) << 4 ) | ( (byte) ( this.isCraftable() ? 1 : 0 ) << 6 ) | ( this.hasTagCompound() ? 1 : 0 ) << 7 ); i.writeByte( mask ); ByteBufUtils.writeTag( i, this.getDefinition().serializeNBT() ); this.putPacketValue( i, this.getStackSize() ); this.putPacketValue( i, this.getCountRequestable() ); } @Override public void add( final IAEItemStack option ) { if( option == null ) { return; } this.incStackSize( option.getStackSize() ); this.setCountRequestable( this.getCountRequestable() + option.getCountRequestable() ); this.setCraftable( this.isCraftable() || option.isCraftable() ); } @Override public boolean fuzzyComparison( final IAEItemStack other, final FuzzyMode mode ) { final ItemStack itemStack = this.getDefinition(); final ItemStack otherStack = other.getDefinition(); return this.fuzzyItemStackComparison( itemStack, otherStack, mode ); } @Override public IAEItemStack copy() { return new AEItemStack( this ); } @Override public boolean isItem() { return true; } @Override public boolean isFluid() { return false; } @Override public IStorageChannel getChannel() { return Api.INSTANCE.storage().getStorageChannel( IItemStorageChannel.class ); } @Override public ItemStack createItemStack() { return ItemHandlerHelper.copyStackWithSize( this.getDefinition(), (int) Math.min( Integer.MAX_VALUE, this.getStackSize() ) ); } @Override public Item getItem() { return this.getDefinition().getItem(); } @Override public int getItemDamage() { return this.sharedStack.getItemDamage(); } @Override public boolean sameOre( final IAEItemStack is ) { return OreHelper.INSTANCE.sameOre( this, is ); } @Override public boolean isSameType( final IAEItemStack otherStack ) { if( otherStack == null ) { return false; } return Objects.equals( this.sharedStack, ( (AEItemStack) otherStack ).sharedStack ); } @Override public boolean isSameType( final ItemStack otherStack ) { if( otherStack.isEmpty() ) { return false; } int oldSize = otherStack.getCount(); otherStack.setCount( 1 ); boolean ret = ItemStack.areItemStacksEqual( this.getDefinition(), otherStack ); otherStack.setCount( oldSize ); return ret; } @Override public int hashCode() { return this.sharedStack.hashCode(); } @Override public boolean equals( final Object ia ) { if( ia instanceof AEItemStack ) { return this.isSameType( (AEItemStack) ia ); } else if( ia instanceof ItemStack ) { // this actually breaks the equals contract (being equals to unrelated classes) return equals( (ItemStack) ia ); } return false; } @Override public boolean equals( final ItemStack is ) { return this.isSameType( is ); } @Override public ItemStack getCachedItemStack( long stackSize ) { if( this.cachedItemStack != null ) { ItemStack currentCached = this.cachedItemStack; this.cachedItemStack = null; currentCached.setCount( Ints.saturatedCast( stackSize ) ); return currentCached; } ItemStack itemStack = this.createItemStack(); itemStack.setCount( Ints.saturatedCast( stackSize ) ); return itemStack; } @Override public void setCachedItemStack( ItemStack itemStack ) { this.cachedItemStack = itemStack; } @Override public String toString() { return this.getStackSize() + "x" + this.getDefinition().getItem().getRegistryName(); } @SideOnly( Side.CLIENT ) public List getToolTip() { if( this.tooltip == null ) { this.tooltip = Platform.getTooltip( this.asItemStackRepresentation() ); } return this.tooltip; } @SideOnly( Side.CLIENT ) public String getDisplayName() { if( this.displayName == null ) { this.displayName = Platform.getItemDisplayName( this.asItemStackRepresentation() ); } return this.displayName; } @SideOnly( Side.CLIENT ) public String getModID() { return this.getDefinition().getItem().getRegistryName().getResourceDomain(); } public Optional getOre() { return this.oreReference; } @Override public boolean hasTagCompound() { return this.getDefinition().hasTagCompound(); } @Override public ItemStack asItemStackRepresentation() { return this.getDefinition().copy(); } @Override public ItemStack getDefinition() { return this.sharedStack.getDefinition(); } AESharedItemStack getSharedStack() { return this.sharedStack; } private boolean fuzzyItemStackComparison( ItemStack a, ItemStack b, FuzzyMode mode ) { if( a.getItem() == b.getItem() && ( a.getItem().isDamageable() || Platform.isGTDamageableItem( a.getItem() ) ) ) { if( mode == FuzzyMode.IGNORE_ALL ) { if( a.getItem().isDamageable() ) { return true; } else if( Platform.isGTDamageableItem( a.getItem() ) ) { return a.getItemDamage() == b.getItemDamage(); } } else if( mode == FuzzyMode.PERCENT_99 ) { if( Platform.isIC2DamageableItem( a.getItem() ) ) { return ( (ICustomDamageItem) a.getItem() ).getCustomDamage( a ) > 1 == ( (ICustomDamageItem) b.getItem() ).getCustomDamage( b ) > 1; } else if( a.getItem().isDamageable() ) { return a.getItemDamage() > 1 == b.getItemDamage() > 1; } else if( Platform.isGTDamageableItem( a.getItem() ) ) { return ( (IToolItem) a.getItem() ).getItemDamage( a ) > 1 == ( (IToolItem) b.getItem() ).getItemDamage( b ) > 1; } } else { float percentDamageOfA = 0; float percentDamageOfB = 0; if( Platform.isIC2DamageableItem( a.getItem() ) ) { percentDamageOfA = (float) ( (ICustomDamageItem) a.getItem() ).getCustomDamage( a ) / ( (ICustomDamageItem) a.getItem() ).getMaxCustomDamage( a ); percentDamageOfB = (float) ( (ICustomDamageItem) b.getItem() ).getCustomDamage( b ) / ( (ICustomDamageItem) b.getItem() ).getMaxCustomDamage( b ); } else if( a.getItem().isDamageable() ) { percentDamageOfA = (float) a.getItemDamage() / a.getMaxDamage(); percentDamageOfB = (float) b.getItemDamage() / b.getMaxDamage(); } else if( Platform.isGTDamageableItem( a.getItem() ) ) { percentDamageOfA = (float) ( (IToolItem) a.getItem() ).getItemDamage( a ) / ( (IToolItem) a.getItem() ).getMaxItemDamage( a ); percentDamageOfB = (float) ( (IToolItem) b.getItem() ).getItemDamage( b ) / ( (IToolItem) b.getItem() ).getMaxItemDamage( b ); } return percentDamageOfA > mode.breakPoint == percentDamageOfB > mode.breakPoint; } } return false; } }