2013-07-29 07:13:03 -04:00
// HopperEntity.cpp
// Implements the cHopperEntity representing a hopper block entity
# include "Globals.h"
# include "HopperEntity.h"
# include "../Chunk.h"
2013-08-19 05:39:13 -04:00
# include "../Entities/Player.h"
2014-02-11 17:54:01 -05:00
# include "../Entities/Pickup.h"
2013-12-08 06:17:54 -05:00
# include "../Bindings/PluginManager.h"
2013-07-29 07:13:03 -04:00
# include "ChestEntity.h"
# include "DropSpenserEntity.h"
# include "FurnaceEntity.h"
2014-02-11 17:54:01 -05:00
# include "../BoundingBox.h"
2014-02-24 16:47:58 -05:00
# include "json/json.h"
2013-07-29 07:13:03 -04:00
cHopperEntity : : cHopperEntity ( int a_BlockX , int a_BlockY , int a_BlockZ , cWorld * a_World ) :
super ( E_BLOCK_HOPPER , a_BlockX , a_BlockY , a_BlockZ , ContentsWidth , ContentsHeight , a_World ) ,
m_LastMoveItemsInTick ( 0 ) ,
m_LastMoveItemsOutTick ( 0 )
{
}
/** Returns the block coords of the block receiving the output items, based on the meta
Returns false if unattached
*/
bool cHopperEntity : : GetOutputBlockPos ( NIBBLETYPE a_BlockMeta , int & a_OutputX , int & a_OutputY , int & a_OutputZ )
{
a_OutputX = m_PosX ;
a_OutputY = m_PosY ;
a_OutputZ = m_PosZ ;
switch ( a_BlockMeta )
{
case E_META_HOPPER_FACING_XM : a_OutputX - - ; return true ;
case E_META_HOPPER_FACING_XP : a_OutputX + + ; return true ;
case E_META_HOPPER_FACING_YM : a_OutputY - - ; return true ;
case E_META_HOPPER_FACING_ZM : a_OutputZ - - ; return true ;
case E_META_HOPPER_FACING_ZP : a_OutputZ + + ; return true ;
default :
{
// Not attached
return false ;
}
}
}
bool cHopperEntity : : Tick ( float a_Dt , cChunk & a_Chunk )
{
2014-02-24 14:29:59 -05:00
UNUSED ( a_Dt ) ;
2013-07-29 07:13:03 -04:00
Int64 CurrentTick = a_Chunk . GetWorld ( ) - > GetWorldAge ( ) ;
bool res = false ;
res = MoveItemsIn ( a_Chunk , CurrentTick ) | | res ;
res = MovePickupsIn ( a_Chunk , CurrentTick ) | | res ;
res = MoveItemsOut ( a_Chunk , CurrentTick ) | | res ;
return res ;
}
void cHopperEntity : : SaveToJson ( Json : : Value & a_Value )
{
2014-02-24 14:29:59 -05:00
UNUSED ( a_Value ) ;
2013-07-29 07:13:03 -04:00
// TODO
LOGWARNING ( " %s: Not implemented yet " , __FUNCTION__ ) ;
}
void cHopperEntity : : SendTo ( cClientHandle & a_Client )
{
// The hopper entity doesn't need anything sent to the client when it's created / gets in the viewdistance
// All the actual handling is in the cWindow UI code that gets called when the hopper is rclked
UNUSED ( a_Client ) ;
}
void cHopperEntity : : UsedBy ( cPlayer * a_Player )
{
// If the window is not created, open it anew:
cWindow * Window = GetWindow ( ) ;
if ( Window = = NULL )
{
OpenNewWindow ( ) ;
Window = GetWindow ( ) ;
}
// Open the window for the player:
if ( Window ! = NULL )
{
if ( a_Player - > GetWindow ( ) ! = Window )
{
a_Player - > OpenWindow ( Window ) ;
}
}
// This is rather a hack
// Instead of marking the chunk as dirty upon chest contents change, we mark it dirty now
// We cannot properly detect contents change, but such a change doesn't happen without a player opening the chest first.
// The few false positives aren't much to worry about
int ChunkX , ChunkZ ;
2013-08-03 14:05:07 -04:00
cChunkDef : : BlockToChunk ( m_PosX , m_PosZ , ChunkX , ChunkZ ) ;
2013-07-29 07:13:03 -04:00
m_World - > MarkChunkDirty ( ChunkX , ChunkZ ) ;
}
/// Opens a new window UI for this hopper
void cHopperEntity : : OpenNewWindow ( void )
{
OpenWindow ( new cHopperWindow ( m_PosX , m_PosY , m_PosZ , this ) ) ;
}
/// Moves items from the container above it into this hopper. Returns true if the contents have changed.
bool cHopperEntity : : MoveItemsIn ( cChunk & a_Chunk , Int64 a_CurrentTick )
{
if ( m_PosY > = cChunkDef : : Height )
{
// This hopper is at the top of the world, no more blocks above
return false ;
}
if ( a_CurrentTick - m_LastMoveItemsInTick < TICKS_PER_TRANSFER )
{
// Too early after the previous transfer
return false ;
}
// Try moving an item in:
bool res = false ;
switch ( a_Chunk . GetBlock ( m_RelX , m_PosY + 1 , m_RelZ ) )
{
2013-08-11 08:57:07 -04:00
case E_BLOCK_CHEST :
{
// Chests have special handling because of double-chests
res = MoveItemsFromChest ( a_Chunk ) ;
break ;
}
case E_BLOCK_LIT_FURNACE :
case E_BLOCK_FURNACE :
{
// Furnaces have special handling because only the output and leftover fuel buckets shall be moved
res = MoveItemsFromFurnace ( a_Chunk ) ;
break ;
}
2013-07-29 07:13:03 -04:00
case E_BLOCK_DISPENSER :
2013-08-11 08:57:07 -04:00
case E_BLOCK_DROPPER :
case E_BLOCK_HOPPER :
{
res = MoveItemsFromGrid ( * ( cBlockEntityWithItems * ) a_Chunk . GetBlockEntity ( m_PosX , m_PosY + 1 , m_PosZ ) ) ;
break ;
}
2013-07-29 07:13:03 -04:00
}
// If the item has been moved, reset the last tick:
if ( res )
{
m_LastMoveItemsInTick = a_CurrentTick ;
}
return res ;
}
/// Moves pickups from above this hopper into it. Returns true if the contents have changed.
bool cHopperEntity : : MovePickupsIn ( cChunk & a_Chunk , Int64 a_CurrentTick )
{
2014-02-11 17:54:01 -05:00
UNUSED ( a_CurrentTick ) ;
class cHopperPickupSearchCallback :
public cEntityCallback
{
public :
2014-02-13 14:57:23 -05:00
cHopperPickupSearchCallback ( const Vector3i & a_Pos , cItemGrid & a_Contents ) :
2014-02-11 17:54:01 -05:00
m_Pos ( a_Pos ) ,
2014-02-12 17:01:22 -05:00
m_bFoundPickupsAbove ( false ) ,
m_Contents ( a_Contents )
2014-02-11 17:54:01 -05:00
{
}
virtual bool Item ( cEntity * a_Entity ) override
{
ASSERT ( a_Entity ! = NULL ) ;
if ( ! a_Entity - > IsPickup ( ) | | a_Entity - > IsDestroyed ( ) )
{
return false ;
}
Vector3f EntityPos = a_Entity - > GetPosition ( ) ;
Vector3f BlockPos ( m_Pos . x + 0.5f , ( float ) m_Pos . y + 1 , m_Pos . z + 0.5f ) ; // One block above hopper, and search from center outwards
float Distance = ( EntityPos - BlockPos ) . Length ( ) ;
if ( Distance < 0.5 )
{
2014-02-12 17:01:22 -05:00
if ( TrySuckPickupIn ( ( cPickup * ) a_Entity ) )
2014-02-11 17:54:01 -05:00
{
2014-02-12 17:01:22 -05:00
return false ;
}
}
return false ;
}
bool TrySuckPickupIn ( cPickup * a_Pickup )
{
for ( int i = 0 ; i < ContentsWidth * ContentsHeight ; i + + )
{
if ( m_Contents . IsSlotEmpty ( i ) )
{
m_bFoundPickupsAbove = true ;
m_Contents . SetSlot ( i , a_Pickup - > GetItem ( ) ) ;
a_Pickup - > Destroy ( ) ; // Kill pickup
2014-02-13 15:20:37 -05:00
2014-02-12 17:01:22 -05:00
return true ;
}
else if ( m_Contents . GetSlot ( i ) . IsEqual ( a_Pickup - > GetItem ( ) ) & & ! m_Contents . GetSlot ( i ) . IsFullStack ( ) )
{
m_bFoundPickupsAbove = true ;
2014-02-13 15:20:37 -05:00
2014-02-12 17:01:22 -05:00
int PreviousCount = m_Contents . GetSlot ( i ) . m_ItemCount ;
a_Pickup - > GetItem ( ) . m_ItemCount - = m_Contents . ChangeSlotCount ( i , a_Pickup - > GetItem ( ) . m_ItemCount ) - PreviousCount ; // Set count to however many items were added
if ( a_Pickup - > GetItem ( ) . IsEmpty ( ) )
2014-02-11 17:54:01 -05:00
{
2014-02-12 17:01:22 -05:00
a_Pickup - > Destroy ( ) ; // Kill pickup if all items were added
2014-02-11 17:54:01 -05:00
}
2014-02-12 17:01:22 -05:00
return true ;
2014-02-11 17:54:01 -05:00
}
}
return false ;
}
bool FoundPickupsAbove ( void ) const
{
return m_bFoundPickupsAbove ;
}
protected :
2014-02-13 14:57:23 -05:00
Vector3i m_Pos ;
2014-02-11 17:54:01 -05:00
bool m_bFoundPickupsAbove ;
cItemGrid & m_Contents ;
} ;
cHopperPickupSearchCallback HopperPickupSearchCallback ( Vector3i ( GetPosX ( ) , GetPosY ( ) , GetPosZ ( ) ) , m_Contents ) ;
a_Chunk . ForEachEntity ( HopperPickupSearchCallback ) ;
return HopperPickupSearchCallback . FoundPickupsAbove ( ) ;
2013-07-29 07:13:03 -04:00
}
/// Moves items out from this hopper into the destination. Returns true if the contents have changed.
bool cHopperEntity : : MoveItemsOut ( cChunk & a_Chunk , Int64 a_CurrentTick )
{
if ( a_CurrentTick - m_LastMoveItemsOutTick < TICKS_PER_TRANSFER )
{
// Too early after the previous transfer
return false ;
}
int bx , by , bz ;
NIBBLETYPE Meta = a_Chunk . GetMeta ( m_RelX , m_PosY , m_RelZ ) ;
if ( ! GetOutputBlockPos ( Meta , bx , by , bz ) )
{
// Not attached to another container
return false ;
}
if ( by < 0 )
{
// Cannot output below the zero-th block level
return false ;
}
// Convert coords to relative:
int rx = bx - a_Chunk . GetPosX ( ) * cChunkDef : : Width ;
int rz = bz - a_Chunk . GetPosZ ( ) * cChunkDef : : Width ;
cChunk * DestChunk = a_Chunk . GetRelNeighborChunkAdjustCoords ( rx , rz ) ;
if ( DestChunk = = NULL )
{
// The destination chunk has been unloaded, don't tick
return false ;
}
// Call proper moving function, based on the blocktype present at the coords:
bool res = false ;
switch ( DestChunk - > GetBlock ( rx , by , rz ) )
{
2013-08-11 08:57:07 -04:00
case E_BLOCK_CHEST :
{
// Chests have special handling because of double-chests
res = MoveItemsToChest ( * DestChunk , bx , by , bz ) ;
break ;
}
case E_BLOCK_LIT_FURNACE :
case E_BLOCK_FURNACE :
{
// Furnaces have special handling because of the direction-to-slot relation
res = MoveItemsToFurnace ( * DestChunk , bx , by , bz , Meta ) ;
break ;
}
2013-07-29 07:13:03 -04:00
case E_BLOCK_DISPENSER :
2013-08-11 08:57:07 -04:00
case E_BLOCK_DROPPER :
case E_BLOCK_HOPPER :
{
res = MoveItemsToGrid ( * ( cBlockEntityWithItems * ) DestChunk - > GetBlockEntity ( bx , by , bz ) ) ;
break ;
}
2013-07-29 07:13:03 -04:00
}
// If the item has been moved, reset the last tick:
if ( res )
{
m_LastMoveItemsOutTick = a_CurrentTick ;
}
return res ;
}
/// Moves items from a chest (dblchest) above the hopper into this hopper. Returns true if contents have changed.
bool cHopperEntity : : MoveItemsFromChest ( cChunk & a_Chunk )
{
2013-08-11 08:57:07 -04:00
if ( MoveItemsFromGrid ( * ( cChestEntity * ) a_Chunk . GetBlockEntity ( m_PosX , m_PosY + 1 , m_PosZ ) ) )
2013-07-29 07:13:03 -04:00
{
// Moved the item from the chest directly above the hopper
return true ;
}
// Check if the chest is a double-chest, if so, try to move from there:
static const struct
{
int x , z ;
}
Coords [ ] =
{
{ 1 , 0 } ,
{ - 1 , 0 } ,
{ 0 , 1 } ,
{ 0 , - 1 } ,
} ;
2013-12-20 10:01:34 -05:00
for ( size_t i = 0 ; i < ARRAYCOUNT ( Coords ) ; i + + )
2013-07-29 07:13:03 -04:00
{
int x = m_RelX + Coords [ i ] . x ;
int z = m_RelZ + Coords [ i ] . z ;
cChunk * Neighbor = a_Chunk . GetRelNeighborChunkAdjustCoords ( x , z ) ;
if (
( Neighbor = = NULL ) | |
( Neighbor - > GetBlock ( x , m_PosY + 1 , z ) ! = E_BLOCK_CHEST )
)
{
continue ;
}
2013-08-11 08:57:07 -04:00
if ( MoveItemsFromGrid ( * ( cChestEntity * ) Neighbor - > GetBlockEntity ( x , m_PosY , z ) ) )
2013-07-29 07:13:03 -04:00
{
return true ;
}
return false ;
}
// The chest was single and nothing could be moved
return false ;
}
/// Moves items from a furnace above the hopper into this hopper. Returns true if contents have changed.
bool cHopperEntity : : MoveItemsFromFurnace ( cChunk & a_Chunk )
{
cFurnaceEntity * Furnace = ( cFurnaceEntity * ) a_Chunk . GetBlockEntity ( m_PosX , m_PosY + 1 , m_PosZ ) ;
ASSERT ( Furnace ! = NULL ) ;
// Try move from the output slot:
2013-08-11 08:57:07 -04:00
if ( MoveItemsFromSlot ( * Furnace , cFurnaceEntity : : fsOutput , true ) )
2013-07-29 07:13:03 -04:00
{
cItem NewOutput ( Furnace - > GetOutputSlot ( ) ) ;
Furnace - > SetOutputSlot ( NewOutput . AddCount ( - 1 ) ) ;
return true ;
}
// No output moved, check if we can move an empty bucket out of the fuel slot:
if ( Furnace - > GetFuelSlot ( ) . m_ItemType = = E_ITEM_BUCKET )
{
2013-08-11 08:57:07 -04:00
if ( MoveItemsFromSlot ( * Furnace , cFurnaceEntity : : fsFuel , true ) )
2013-07-29 07:13:03 -04:00
{
Furnace - > SetFuelSlot ( cItem ( ) ) ;
return true ;
}
}
// Nothing can be moved
return false ;
}
2013-08-11 08:57:07 -04:00
bool cHopperEntity : : MoveItemsFromGrid ( cBlockEntityWithItems & a_Entity )
2013-07-29 07:13:03 -04:00
{
2013-08-11 08:57:07 -04:00
cItemGrid & Grid = a_Entity . GetContents ( ) ;
int NumSlots = Grid . GetNumSlots ( ) ;
2013-07-29 07:13:03 -04:00
// First try adding items of types already in the hopper:
for ( int i = 0 ; i < NumSlots ; i + + )
{
2013-08-11 08:57:07 -04:00
if ( Grid . IsSlotEmpty ( i ) )
2013-07-29 07:13:03 -04:00
{
continue ;
}
2013-08-11 08:57:07 -04:00
if ( MoveItemsFromSlot ( a_Entity , i , false ) )
2013-07-29 07:13:03 -04:00
{
2013-08-11 08:57:07 -04:00
Grid . ChangeSlotCount ( i , - 1 ) ;
2013-07-29 07:13:03 -04:00
return true ;
}
}
// No already existing stack can be topped up, try again with allowing new stacks:
for ( int i = 0 ; i < NumSlots ; i + + )
{
2013-08-11 08:57:07 -04:00
if ( Grid . IsSlotEmpty ( i ) )
2013-07-29 07:13:03 -04:00
{
continue ;
}
2013-08-11 08:57:07 -04:00
if ( MoveItemsFromSlot ( a_Entity , i , true ) )
2013-07-29 07:13:03 -04:00
{
2013-08-11 08:57:07 -04:00
Grid . ChangeSlotCount ( i , - 1 ) ;
2013-07-29 07:13:03 -04:00
return true ;
}
}
return false ;
}
2013-08-11 08:57:07 -04:00
/// Moves one piece of the specified a_Entity's slot itemstack into this hopper. Returns true if contents have changed. Doesn't change the itemstack.
bool cHopperEntity : : MoveItemsFromSlot ( cBlockEntityWithItems & a_Entity , int a_SlotNum , bool a_AllowNewStacks )
2013-07-29 07:13:03 -04:00
{
2013-08-11 08:57:07 -04:00
cItem One ( a_Entity . GetSlot ( a_SlotNum ) . CopyOne ( ) ) ;
for ( int i = 0 ; i < ContentsWidth * ContentsHeight ; i + + )
2013-07-29 07:13:03 -04:00
{
2013-08-11 08:57:07 -04:00
if ( m_Contents . IsSlotEmpty ( i ) )
{
if ( a_AllowNewStacks )
{
if ( cPluginManager : : Get ( ) - > CallHookHopperPullingItem ( * m_World , * this , i , a_Entity , a_SlotNum ) )
{
// Plugin disagrees with the move
continue ;
}
}
m_Contents . SetSlot ( i , One ) ;
return true ;
}
2014-01-16 14:00:49 -05:00
else if ( m_Contents . GetSlot ( i ) . IsEqual ( One ) )
2013-08-11 08:57:07 -04:00
{
if ( cPluginManager : : Get ( ) - > CallHookHopperPullingItem ( * m_World , * this , i , a_Entity , a_SlotNum ) )
{
// Plugin disagrees with the move
continue ;
}
m_Contents . ChangeSlotCount ( i , 1 ) ;
return true ;
}
2013-07-29 07:13:03 -04:00
}
return false ;
}
/// Moves items to the chest at the specified coords. Returns true if contents have changed
bool cHopperEntity : : MoveItemsToChest ( cChunk & a_Chunk , int a_BlockX , int a_BlockY , int a_BlockZ )
{
// Try the chest directly connected to the hopper:
2013-08-11 08:57:07 -04:00
if ( MoveItemsToGrid ( * ( cChestEntity * ) a_Chunk . GetBlockEntity ( a_BlockX , a_BlockY , a_BlockZ ) ) )
2013-07-29 07:13:03 -04:00
{
return true ;
}
// Check if the chest is a double-chest, if so, try to move into the other half:
static const struct
{
int x , z ;
}
Coords [ ] =
{
{ 1 , 0 } ,
{ - 1 , 0 } ,
{ 0 , 1 } ,
{ 0 , - 1 } ,
} ;
2013-12-20 10:01:34 -05:00
for ( size_t i = 0 ; i < ARRAYCOUNT ( Coords ) ; i + + )
2013-07-29 07:13:03 -04:00
{
int x = m_RelX + Coords [ i ] . x ;
int z = m_RelZ + Coords [ i ] . z ;
cChunk * Neighbor = a_Chunk . GetRelNeighborChunkAdjustCoords ( x , z ) ;
if (
( Neighbor = = NULL ) | |
( Neighbor - > GetBlock ( x , m_PosY + 1 , z ) ! = E_BLOCK_CHEST )
)
{
continue ;
}
2013-08-11 08:57:07 -04:00
if ( MoveItemsToGrid ( * ( cChestEntity * ) Neighbor - > GetBlockEntity ( a_BlockX , a_BlockY , a_BlockZ ) ) )
2013-07-29 07:13:03 -04:00
{
return true ;
}
return false ;
}
// The chest was single and nothing could be moved
return false ;
}
/// Moves items to the furnace at the specified coords. Returns true if contents have changed
bool cHopperEntity : : MoveItemsToFurnace ( cChunk & a_Chunk , int a_BlockX , int a_BlockY , int a_BlockZ , NIBBLETYPE a_HopperMeta )
{
cFurnaceEntity * Furnace = ( cFurnaceEntity * ) a_Chunk . GetBlockEntity ( a_BlockX , a_BlockY , a_BlockZ ) ;
if ( a_HopperMeta = = E_META_HOPPER_FACING_YM )
{
// Feed the input slot of the furnace
2013-08-11 08:57:07 -04:00
return MoveItemsToSlot ( * Furnace , cFurnaceEntity : : fsInput ) ;
2013-07-29 07:13:03 -04:00
}
else
{
// Feed the fuel slot of the furnace
2013-08-11 08:57:07 -04:00
return MoveItemsToSlot ( * Furnace , cFurnaceEntity : : fsFuel ) ;
2013-07-29 07:13:03 -04:00
}
}
2013-08-11 08:57:07 -04:00
bool cHopperEntity : : MoveItemsToGrid ( cBlockEntityWithItems & a_Entity )
2013-07-29 07:13:03 -04:00
{
// Iterate through our slots, try to move from each one:
2013-08-11 08:57:07 -04:00
int NumSlots = a_Entity . GetContents ( ) . GetNumSlots ( ) ;
for ( int i = 0 ; i < NumSlots ; i + + )
2013-07-29 07:13:03 -04:00
{
2013-08-11 08:57:07 -04:00
if ( MoveItemsToSlot ( a_Entity , i ) )
2013-07-29 07:13:03 -04:00
{
return true ;
}
}
return false ;
}
2013-08-11 08:57:07 -04:00
bool cHopperEntity : : MoveItemsToSlot ( cBlockEntityWithItems & a_Entity , int a_DstSlotNum )
2013-07-29 07:13:03 -04:00
{
2013-08-11 08:57:07 -04:00
cItemGrid & Grid = a_Entity . GetContents ( ) ;
if ( Grid . IsSlotEmpty ( a_DstSlotNum ) )
2013-07-29 07:13:03 -04:00
{
// The slot is empty, move the first non-empty slot from our contents:
for ( int i = 0 ; i < ContentsWidth * ContentsHeight ; i + + )
{
if ( ! m_Contents . IsSlotEmpty ( i ) )
{
2013-08-11 08:57:07 -04:00
if ( cPluginManager : : Get ( ) - > CallHookHopperPushingItem ( * m_World , * this , i , a_Entity , a_DstSlotNum ) )
{
// A plugin disagrees with the move
continue ;
}
Grid . SetSlot ( a_DstSlotNum , m_Contents . GetSlot ( i ) . CopyOne ( ) ) ;
2013-07-29 07:13:03 -04:00
m_Contents . ChangeSlotCount ( i , - 1 ) ;
return true ;
}
}
return false ;
}
else
{
// The slot is taken, try to top it up:
2013-08-11 08:57:07 -04:00
const cItem & DestSlot = Grid . GetSlot ( a_DstSlotNum ) ;
2013-07-29 07:13:03 -04:00
if ( DestSlot . IsFullStack ( ) )
{
return false ;
}
for ( int i = 0 ; i < ContentsWidth * ContentsHeight ; i + + )
{
2014-01-16 14:00:49 -05:00
if ( m_Contents . GetSlot ( i ) . IsEqual ( DestSlot ) )
2013-07-29 07:13:03 -04:00
{
2013-08-11 08:57:07 -04:00
if ( cPluginManager : : Get ( ) - > CallHookHopperPushingItem ( * m_World , * this , i , a_Entity , a_DstSlotNum ) )
{
// A plugin disagrees with the move
continue ;
}
Grid . ChangeSlotCount ( a_DstSlotNum , 1 ) ;
2013-07-29 07:13:03 -04:00
m_Contents . ChangeSlotCount ( i , - 1 ) ;
return true ;
}
}
return false ;
}
}