1e20086799
* Extracts item comparison from Platform into their own helper. Renamed methods to be more more fitting for the actual comparison. Added documentation about each methods behaviour.
444 lines
12 KiB
Java
444 lines
12 KiB
Java
/*
|
|
* This file is part of Applied Energistics 2.
|
|
* Copyright (c) 2013 - 2014, 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 <http://www.gnu.org/licenses/lgpl>.
|
|
*/
|
|
|
|
package appeng.util.helpers;
|
|
|
|
|
|
import java.lang.reflect.Field;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import net.minecraft.item.Item;
|
|
import net.minecraft.item.ItemStack;
|
|
import net.minecraft.nbt.NBTBase;
|
|
import net.minecraft.nbt.NBTPrimitive;
|
|
import net.minecraft.nbt.NBTTagCompound;
|
|
import net.minecraft.nbt.NBTTagList;
|
|
import net.minecraft.nbt.NBTTagString;
|
|
import net.minecraftforge.oredict.OreDictionary;
|
|
|
|
import appeng.api.config.FuzzyMode;
|
|
import appeng.core.AELog;
|
|
import appeng.util.item.AESharedNBT;
|
|
import appeng.util.item.OreHelper;
|
|
import appeng.util.item.OreReference;
|
|
|
|
|
|
/**
|
|
* A helper class for comparing {@link Item}, {@link ItemStack} or NBT
|
|
*
|
|
*/
|
|
public class ItemComparisonHelper
|
|
{
|
|
|
|
private static Field tagList;
|
|
|
|
/**
|
|
* Compare the two {@link ItemStack}s based on the same {@link Item} and damage value.
|
|
*
|
|
* In case of the item being damageable, only the {@link Item} will be considered.
|
|
* If not it will also compare both damage values.
|
|
*
|
|
* Ignores NBT.
|
|
*
|
|
* @return true, if both are equal.
|
|
*/
|
|
public boolean isEqualItemType( final ItemStack that, final ItemStack other )
|
|
{
|
|
if( that != null && other != null && that.getItem() == other.getItem() )
|
|
{
|
|
if( that.isItemStackDamageable() )
|
|
{
|
|
return true;
|
|
}
|
|
return that.getItemDamage() == other.getItemDamage();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* A wrapper around {@link ItemStack#isItemEqual(ItemStack)}.
|
|
*
|
|
* The benefit is to compare two null item stacks, without any additional null checks.
|
|
*
|
|
* Ignores NBT.
|
|
*
|
|
* @return true, if both are equal.
|
|
*/
|
|
public boolean isEqualItem( @Nullable final ItemStack left, @Nullable final ItemStack right )
|
|
{
|
|
return left != null && right != null && left.isItemEqual( right );
|
|
}
|
|
|
|
/**
|
|
* Compares two {@link ItemStack} and their NBT tag for equality.
|
|
*
|
|
* Use this when a precise check is required and the same item is required.
|
|
* Not just something with different NBT tags.
|
|
*
|
|
* @return true, if both are identical.
|
|
*/
|
|
public boolean isSameItem( @Nullable final ItemStack is, @Nullable final ItemStack filter )
|
|
{
|
|
return isEqualItem( is, filter ) && hasSameNbtTag( is, filter );
|
|
}
|
|
|
|
/**
|
|
* Similar to {@link ItemComparisonHelper#isEqualItem(ItemStack, ItemStack)},
|
|
* but it can further check, if both match the same {@link FuzzyMode}
|
|
* or are considered equal by the {@link OreDictionary}
|
|
*
|
|
* @param mode how to compare the two {@link ItemStack}s
|
|
* @return true, if both are matching the mode or considered equal by the {@link OreDictionary}
|
|
*/
|
|
public boolean isFuzzyEqualItem( final ItemStack a, final ItemStack b, final FuzzyMode mode )
|
|
{
|
|
if( a == null && b == null )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if( a == null || b == null )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* if ( a.itemID != 0 && b.itemID != 0 && a.isItemStackDamageable() && ! a.getHasSubtypes() && a.itemID ==
|
|
* b.itemID ) { return (a.getItemDamage() > 0) == (b.getItemDamage() > 0); }
|
|
*/
|
|
|
|
// test damageable items..
|
|
if( a.getItem() != null && b.getItem() != null && a.getItem().isDamageable() && a.getItem() == b.getItem() )
|
|
{
|
|
try
|
|
{
|
|
if( mode == FuzzyMode.IGNORE_ALL )
|
|
{
|
|
return true;
|
|
}
|
|
else if( mode == FuzzyMode.PERCENT_99 )
|
|
{
|
|
final Item ai = a.getItem();
|
|
final Item bi = b.getItem();
|
|
|
|
return ( ai.getDurabilityForDisplay( a ) > 1 ) == ( bi.getDurabilityForDisplay( b ) > 1 );
|
|
}
|
|
else
|
|
{
|
|
final Item ai = a.getItem();
|
|
final Item bi = b.getItem();
|
|
|
|
final float percentDamagedOfA = 1.0f - (float) ai.getDurabilityForDisplay( a );
|
|
final float percentDamagedOfB = 1.0f - (float) bi.getDurabilityForDisplay( b );
|
|
|
|
return ( percentDamagedOfA > mode.breakPoint ) == ( percentDamagedOfB > mode.breakPoint );
|
|
}
|
|
}
|
|
catch( final Throwable e )
|
|
{
|
|
if( mode == FuzzyMode.IGNORE_ALL )
|
|
{
|
|
return true;
|
|
}
|
|
else if( mode == FuzzyMode.PERCENT_99 )
|
|
{
|
|
return ( a.getItemDamage() > 1 ) == ( b.getItemDamage() > 1 );
|
|
}
|
|
else
|
|
{
|
|
final float percentDamagedOfA = (float) a.getItemDamage() / (float) a.getMaxDamage();
|
|
final float percentDamagedOfB = (float) b.getItemDamage() / (float) b.getMaxDamage();
|
|
|
|
return ( percentDamagedOfA > mode.breakPoint ) == ( percentDamagedOfB > mode.breakPoint );
|
|
}
|
|
}
|
|
}
|
|
|
|
final OreReference aOR = OreHelper.INSTANCE.isOre( a );
|
|
final OreReference bOR = OreHelper.INSTANCE.isOre( b );
|
|
|
|
if( OreHelper.INSTANCE.sameOre( aOR, bOR ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* // test ore dictionary.. int OreID = getOreID( a ); if ( OreID != -1 ) return OreID == getOreID( b );
|
|
* if ( Mode != FuzzyMode.IGNORE_ALL ) { if ( a.hasTagCompound() && !isShared( a.getTagCompound() ) ) { a =
|
|
* Platform.getSharedItemStack( AEItemStack.create( a ) ); }
|
|
* if ( b.hasTagCompound() && !isShared( b.getTagCompound() ) ) { b = Platform.getSharedItemStack(
|
|
* AEItemStack.create( b ) ); }
|
|
* // test regular items with damage values and what not... if ( isShared( a.getTagCompound() ) && isShared(
|
|
* b.getTagCompound() ) && a.itemID == b.itemID ) { return ((AppEngSharedNBTTagCompound)
|
|
* a.getTagCompound()).compareFuzzyWithRegistry( (AppEngSharedNBTTagCompound) b.getTagCompound() ); } }
|
|
*/
|
|
|
|
return a.isItemEqual( b );
|
|
}
|
|
|
|
/**
|
|
* recursive test for NBT Equality, this was faster then trying to compare / generate hashes, its also more reliable
|
|
* then the vanilla version which likes to fail when NBT Compound data changes order, it is pretty expensive
|
|
* performance wise, so try an use shared tag compounds as long as the system remains in AE.
|
|
*/
|
|
public boolean isNbtTagEqual( final NBTBase left, final NBTBase right )
|
|
{
|
|
// same type?
|
|
final byte id = left.getId();
|
|
if( id == right.getId() )
|
|
{
|
|
switch( id )
|
|
{
|
|
case 10:
|
|
{
|
|
final NBTTagCompound ctA = (NBTTagCompound) left;
|
|
final NBTTagCompound ctB = (NBTTagCompound) right;
|
|
|
|
final Set<String> cA = ctA.getKeySet();
|
|
final Set<String> cB = ctB.getKeySet();
|
|
|
|
if( cA.size() != cB.size() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for( final String name : cA )
|
|
{
|
|
final NBTBase tag = ctA.getTag( name );
|
|
final NBTBase aTag = ctB.getTag( name );
|
|
if( aTag == null )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !isNbtTagEqual( tag, aTag ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case 9: // ) // A instanceof NBTTagList )
|
|
{
|
|
final NBTTagList lA = (NBTTagList) left;
|
|
final NBTTagList lB = (NBTTagList) right;
|
|
if( lA.tagCount() != lB.tagCount() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
final List<NBTBase> tag = tagList( lA );
|
|
final List<NBTBase> aTag = tagList( lB );
|
|
if( tag.size() != aTag.size() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for( int x = 0; x < tag.size(); x++ )
|
|
{
|
|
if( aTag.get( x ) == null )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !isNbtTagEqual( tag.get( x ), aTag.get( x ) ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case 1: // NBTTagByte
|
|
return ( (NBTPrimitive) left ).getByte() == ( (NBTPrimitive) right ).getByte();
|
|
|
|
case 4: // NBTTagLong
|
|
return ( (NBTPrimitive) left ).getLong() == ( (NBTPrimitive) right ).getLong();
|
|
|
|
case 8: // NBTTagString
|
|
return ( (NBTTagString) left ).getString().equals( ( (NBTTagString) right ).getString() );
|
|
|
|
case 6: // NBTTagDouble
|
|
return ( (NBTPrimitive) left ).getDouble() == ( (NBTPrimitive) right ).getDouble();
|
|
|
|
case 5: // NBTTagFloat
|
|
return ( (NBTPrimitive) left ).getFloat() == ( (NBTPrimitive) right ).getFloat();
|
|
|
|
case 3: // NBTTagInt
|
|
return ( (NBTPrimitive) left ).getInt() == ( (NBTPrimitive) right ).getInt();
|
|
|
|
default:
|
|
return left.equals( right );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Unordered hash of NBT Data, used to work thought huge piles fast, but ignores the order just in case MC
|
|
* decided to change it... WHICH IS BAD...
|
|
*/
|
|
public int createUnorderedNbtHash( final NBTBase nbt )
|
|
{
|
|
// same type?
|
|
int hash = 0;
|
|
final byte id = nbt.getId();
|
|
hash += id;
|
|
switch( id )
|
|
{
|
|
case 10:
|
|
{
|
|
final NBTTagCompound ctA = (NBTTagCompound) nbt;
|
|
|
|
final Set<String> cA = ctA.getKeySet();
|
|
|
|
for( final String name : cA )
|
|
{
|
|
hash += name.hashCode() ^ createUnorderedNbtHash( ctA.getTag( name ) );
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
case 9: // ) // A instanceof NBTTagList )
|
|
{
|
|
final NBTTagList lA = (NBTTagList) nbt;
|
|
hash += 9 * lA.tagCount();
|
|
|
|
final List<NBTBase> l = tagList( lA );
|
|
for( int x = 0; x < l.size(); x++ )
|
|
{
|
|
hash += ( (Integer) x ).hashCode() ^ createUnorderedNbtHash( l.get( x ) );
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
case 1: // NBTTagByte
|
|
return hash + ( (NBTPrimitive) nbt ).getByte();
|
|
|
|
case 4: // NBTTagLong
|
|
return hash + (int) ( (NBTPrimitive) nbt ).getLong();
|
|
|
|
case 8: // NBTTagString
|
|
return hash + ( (NBTTagString) nbt ).getString().hashCode();
|
|
|
|
case 6: // NBTTagDouble
|
|
return hash + (int) ( (NBTPrimitive) nbt ).getDouble();
|
|
|
|
case 5: // NBTTagFloat
|
|
return hash + (int) ( (NBTPrimitive) nbt ).getFloat();
|
|
|
|
case 3: // NBTTagInt
|
|
return hash + ( (NBTPrimitive) nbt ).getInt();
|
|
|
|
default:
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lots of silliness to try and account for weird tag related junk, basically requires that two tags have at least
|
|
* something in their tags before it wastes its time comparing them.
|
|
*/
|
|
private boolean hasSameNbtTag( final ItemStack a, final ItemStack b )
|
|
{
|
|
if( a == null && b == null )
|
|
{
|
|
return true;
|
|
}
|
|
if( a == null || b == null )
|
|
{
|
|
return false;
|
|
}
|
|
if( a == b )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
final NBTTagCompound ta = a.getTagCompound();
|
|
final NBTTagCompound tb = b.getTagCompound();
|
|
if( ta == tb )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if( ( ta == null && tb == null ) || ( ta != null && ta.hasNoTags() && tb == null ) || ( tb != null && tb.hasNoTags() && ta == null ) || ( ta != null && ta.hasNoTags() && tb != null && tb.hasNoTags() ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if( ( ta == null && tb != null ) || ( ta != null && tb == null ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if both tags are shared this is easy...
|
|
if( AESharedNBT.isShared( ta ) && AESharedNBT.isShared( tb ) )
|
|
{
|
|
return ta == tb;
|
|
}
|
|
|
|
return isNbtTagEqual( ta, tb );
|
|
}
|
|
|
|
private List<NBTBase> tagList( final NBTTagList lB )
|
|
{
|
|
if( tagList == null )
|
|
{
|
|
try
|
|
{
|
|
tagList = lB.getClass().getDeclaredField( "tagList" );
|
|
}
|
|
catch( final Throwable t )
|
|
{
|
|
try
|
|
{
|
|
tagList = lB.getClass().getDeclaredField( "field_74747_a" );
|
|
}
|
|
catch( final Throwable z )
|
|
{
|
|
AELog.debug( t );
|
|
AELog.debug( z );
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
tagList.setAccessible( true );
|
|
return (List<NBTBase>) tagList.get( lB );
|
|
}
|
|
catch( final Throwable t )
|
|
{
|
|
AELog.debug( t );
|
|
}
|
|
|
|
return new ArrayList<NBTBase>();
|
|
}
|
|
|
|
}
|