summaryrefslogblamecommitdiffstats
path: root/src/BlockEntities/HopperEntity.cpp
blob: 5b273b13c835610fb1208f2609455100b4f9db1e (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                                  
                               
                               
                                      
                               
                        





                          






                                                                              
                                                                                                              
                                                                                       
                                 

                                  
 
                                              





 








                                           
                                                                                  
 
                            

                            




                                                                            


                                       
                                            







                 

                                                        
                               
                                                               







                                                            
                                                                          
 
                     
 
                             

                      



                                                                           
         
                       





 


                                                                                                                  
                                                                                                              
 






                         
                                              
 

                                                                      

                                                      
                              



                                     
 
                                          
                              


                                                    
                                                      






                                                                                                                                

                                                                      
                    





 

                                       
                                            





 
                                                                                    
 
                                         




                                                                               
                                                                         



                                                        
 

                                 
                                                        
         
                                           












                                                                                                                          
                                       


                                     
                                                                                                                                         

                              
         
 




                                                           
 






                   
                                                   
 
                                         

               
                                                                                     
                                     

                                                    


                 
                                                     
                 
                                                 



                                             
                                                                    
                                                                                                                                                                       
                                                                          


                                           
                                                                                      
                                 






                                                     
                                                        
                 
                                                          
 




                                                                                
                                                                    
                                                                           
 

                                                    
                                                                                                                     

                                                                    
 
                                                                                              
 
                                                                                                                                                                           
 
                                                           
                                         
                                                                                                           
                                         
                                                    

                                 








                                                    
                               







                                                                                                                      





 
                                                                                     
 
                                                                          



                                                        
 
                                                             


                                                 



                                                    
                             



                                                              
 
                                      
                                                                  
                                                                           
                                 



                                                                      
 

                                                                                     

                                                                
         
                                           


                                                                                
                                                                     





                                                                                                   
                                                                             

                              
                                       


                                     

                                                                                                                     
                         
                                                                                                                               

                                             
                                                            

                              
         
 




                                                           
 






                   

                                                        


                                                                                       
         
                                                                                                                

                             

                                                   
         
                                                  
                 
                                    
                 





                                                                                 
                 









                                                                  


                    




                                                          


                                            
                 
         
                              






                     

                                                          

                                                                                              
         
                                                                                                                         

                             
 
                                         
                                                                  
         

                                                               

                            
 
                                                                                      
                                                               
         
                                                                        
                 
                                                      


                                    
 







                               
                                                                       
 
                                             
                                          
 

                                          
                                        


                                 
                                                   
                 
                                                    









                                    
                                                                                      
 

                                                                
         

                                              
                                                                                                                      
                         

                                                                 
                         
 


                                                   
                                                            





                                                                                                                      
 
                                                                               
                                                         
 
                                                                                   



                                                                                                             
                 







                     
                                                                         

                                                          
                                                                                            
                                      
         
                                                                                                                

                             

                                                        
         
                                                     
                 
                                    
                 




                                                                                 
                 







                                                             
                 
                    
                 







                                                             
                 
         






                     
                                                                                                    
 
                                                                                       


                                                     
                                                                          



                                                    
                                                                         
         





 
                                                                     

                                                                

                                                            
         
                                                 
                 









                                    
                                                                                       
 

                                                  





                                                                                      





                                                                                                                                 








                                                                  
                                                                    





                                                                        
                                                                    
                         





                                                                                                                                 






                                                                  



 

// HopperEntity.cpp

// Implements the cHopperEntity representing a hopper block entity

#include "Globals.h"
#include "HopperEntity.h"
#include "../Chunk.h"
#include "../Entities/Player.h"
#include "../Entities/Pickup.h"
#include "../Bindings/PluginManager.h"
#include "../UI/HopperWindow.h"
#include "ChestEntity.h"
#include "FurnaceEntity.h"





// How many ticks at minimum between two item transfers to or from the hopper.
#define TICKS_PER_TRANSFER 8_tick





cHopperEntity::cHopperEntity(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, Vector3i a_Pos, cWorld * a_World):
	Super(a_BlockType, a_BlockMeta, a_Pos, ContentsWidth, ContentsHeight, a_World),
	m_LastMoveItemsInTick(0),
	m_LastMoveItemsOutTick(0),
	m_Locked(false)
{
	ASSERT(a_BlockType == E_BLOCK_HOPPER);
}





void cHopperEntity::SetLocked(bool a_Value)
{
	m_Locked = a_Value;
}





std::pair<bool, Vector3i> cHopperEntity::GetOutputBlockPos(NIBBLETYPE a_BlockMeta)
{
	auto pos = GetPos();
	switch (a_BlockMeta)
	{
		case E_META_HOPPER_FACING_XM: return {true, pos.addedX(-1)};
		case E_META_HOPPER_FACING_XP: return {true, pos.addedX( 1)};
		case E_META_HOPPER_FACING_YM: return {true, pos.addedY(-1)};
		case E_META_HOPPER_FACING_ZM: return {true, pos.addedZ(-1)};
		case E_META_HOPPER_FACING_ZP: return {true, pos.addedZ( 1)};
		default:
		{
			// Not attached
			return {false, pos};
		}
	}
}





void cHopperEntity::CopyFrom(const cBlockEntity & a_Src)
{
	Super::CopyFrom(a_Src);
	auto & src = static_cast<const cHopperEntity &>(a_Src);
	m_LastMoveItemsInTick = src.m_LastMoveItemsInTick;
	m_LastMoveItemsOutTick = src.m_LastMoveItemsOutTick;
}





bool cHopperEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	UNUSED(a_Dt);

	bool isDirty = false;
	if (!m_Locked)
	{
		const auto CurrentTick = a_Chunk.GetWorld()->GetWorldAge();
		isDirty = MoveItemsIn(a_Chunk, CurrentTick) || isDirty;
		isDirty = MovePickupsIn(a_Chunk) || isDirty;
		isDirty = MoveItemsOut(a_Chunk, CurrentTick) || isDirty;
	}
	return isDirty;
}





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 right-clicked

	UNUSED(a_Client);
}





bool cHopperEntity::UsedBy(cPlayer * a_Player)
{
	a_Player->GetStatManager().AddValue(Statistic::InspectHopper);

	// If the window is not created, open it anew:
	cWindow * Window = GetWindow();
	if (Window == nullptr)
	{
		OpenNewWindow();
		Window = GetWindow();
	}

	// Open the window for the player:
	if (Window != nullptr)
	{
		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
	cChunkCoords ChunkPos = cChunkDef::BlockToChunk(GetPos());
	m_World->MarkChunkDirty(ChunkPos.m_ChunkX, ChunkPos.m_ChunkZ);
	return true;
}





void cHopperEntity::OpenNewWindow(void)
{
	OpenWindow(new cHopperWindow(this));
}





bool cHopperEntity::MoveItemsIn(cChunk & a_Chunk, const cTickTimeLong a_CurrentTick)
{
	if (m_Pos.y >= 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(GetRelPos().addedY(1)))
	{
		case E_BLOCK_TRAPPED_CHEST:
		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;
		}
		case E_BLOCK_DISPENSER:
		case E_BLOCK_DROPPER:
		case E_BLOCK_HOPPER:
		{
			res = MoveItemsFromGrid(*static_cast<cBlockEntityWithItems *>(a_Chunk.GetBlockEntity(this->GetPos().addedY(1))));
			break;
		}
	}

	// If the item has been moved, reset the last tick:
	if (res)
	{
		m_LastMoveItemsInTick = a_CurrentTick;
	}

	return res;
}





bool cHopperEntity::MovePickupsIn(cChunk & a_Chunk)
{
	class cHopperPickupSearchCallback
	{
	public:
		cHopperPickupSearchCallback(Vector3i a_Pos, cItemGrid & a_Contents) :
			m_Pos(a_Pos),
			m_bFoundPickupsAbove(false),
			m_Contents(a_Contents)
		{
		}

		bool operator () (cEntity & a_Entity)
		{
			if (!a_Entity.IsPickup())
			{
				return false;
			}

			Vector3f EntityPos = a_Entity.GetPosition();
			Vector3f BlockPos(m_Pos.x + 0.5f, static_cast<float>(m_Pos.y) + 1, m_Pos.z + 0.5f);  // One block above hopper, and search from center outwards
			double Distance = (EntityPos - BlockPos).Length();

			if (Distance < 0.5)
			{
				if (TrySuckPickupIn(static_cast<cPickup &>(a_Entity)))
				{
					return false;
				}
			}

			return false;
		}

		bool TrySuckPickupIn(cPickup & a_Pickup)
		{
			cItem & Item = a_Pickup.GetItem();

			for (int i = 0; i < ContentsWidth * ContentsHeight; i++)
			{
				if (m_Contents.IsSlotEmpty(i))
				{
					m_bFoundPickupsAbove = true;
					m_Contents.SetSlot(i, Item);
					a_Pickup.Destroy();  // Kill pickup

					return true;
				}
				else if (m_Contents.GetSlot(i).IsEqual(Item) && !m_Contents.GetSlot(i).IsFullStack())
				{
					m_bFoundPickupsAbove = true;

					int PreviousCount = m_Contents.GetSlot(i).m_ItemCount;

					Item.m_ItemCount -= m_Contents.ChangeSlotCount(i, Item.m_ItemCount) - PreviousCount;  // Set count to however many items were added

					if (Item.IsEmpty())
					{
						a_Pickup.Destroy();  // Kill pickup if all items were added
					}
					return true;
				}
			}
			return false;
		}

		bool FoundPickupsAbove(void) const
		{
			return m_bFoundPickupsAbove;
		}

	protected:
		Vector3i m_Pos;
		bool m_bFoundPickupsAbove;
		cItemGrid & m_Contents;
	};

	cHopperPickupSearchCallback HopperPickupSearchCallback(Vector3i(GetPosX(), GetPosY(), GetPosZ()), m_Contents);
	a_Chunk.ForEachEntity(HopperPickupSearchCallback);

	return HopperPickupSearchCallback.FoundPickupsAbove();
}





bool cHopperEntity::MoveItemsOut(cChunk & a_Chunk, const cTickTimeLong a_CurrentTick)
{
	if ((a_CurrentTick - m_LastMoveItemsOutTick) < TICKS_PER_TRANSFER)
	{
		// Too early after the previous transfer
		return false;
	}

	// Get the coords of the block where to output items:
	auto meta = a_Chunk.GetMeta(GetRelPos());
	auto out = GetOutputBlockPos(meta);
	if (!out.first)
	{
		// Not attached to another container
		return false;
	}
	if (out.second.y < 0)
	{
		// Cannot output below the zero-th block level
		return false;
	}

	// Convert coords to relative:
	auto relCoord = cChunkDef::AbsoluteToRelative(out.second);
	auto destChunk = a_Chunk.GetRelNeighborChunkAdjustCoords(relCoord);
	if (destChunk == nullptr)
	{
		// 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;
	auto absCoord = destChunk->RelativeToAbsolute(relCoord);
	switch (destChunk->GetBlock(relCoord))
	{
		case E_BLOCK_TRAPPED_CHEST:
		case E_BLOCK_CHEST:
		{
			// Chests have special handling because of double-chests
			res = MoveItemsToChest(*destChunk, absCoord);
			break;
		}
		case E_BLOCK_LIT_FURNACE:
		case E_BLOCK_FURNACE:
		{
			// Furnaces have special handling because of the direction-to-slot relation
			res = MoveItemsToFurnace(*destChunk, absCoord, meta);
			break;
		}
		case E_BLOCK_DISPENSER:
		case E_BLOCK_DROPPER:
		case E_BLOCK_HOPPER:
		{
			auto blockEntity = static_cast<cBlockEntityWithItems *>(destChunk->GetBlockEntity(absCoord));
			if (blockEntity == nullptr)
			{
				FLOGWARNING("{0}: A block entity was not found where expected at {1}", __FUNCTION__, absCoord);
				return false;
			}
			res = MoveItemsToGrid(*blockEntity);
			break;
		}
	}

	// If the item has been moved, reset the last tick:
	if (res)
	{
		m_LastMoveItemsOutTick = a_CurrentTick;
	}

	return res;
}





bool cHopperEntity::MoveItemsFromChest(cChunk & a_Chunk)
{
	auto ChestPos = GetPos().addedY(1);
	auto MainChest = static_cast<cChestEntity *>(a_Chunk.GetBlockEntity(ChestPos));
	if (MainChest == nullptr)
	{
		FLOGWARNING("{0}: A chest entity was not found where expected, at {1}", __FUNCTION__, ChestPos);
		return false;
	}
	auto SideChest = MainChest->GetNeighbour();
	if (SideChest == nullptr)
	{
		if (MoveItemsFromGrid(*MainChest))
		{
			return true;
		}
	}
	else
	{
		auto SideAbsCoords = SideChest->GetPos();
		// the "primary" chest is the one with the higher z or x value
		if (SideAbsCoords.z > ChestPos.z || SideAbsCoords.x > ChestPos.x)
		{
			// side is "primary" so pull from it first
			if (MoveItemsFromGrid(*SideChest))
			{
				return true;
			}
			// main is secondary, pull from next
			if (MoveItemsFromGrid(*MainChest))
			{
				return true;
			}
		}
		else
		{
			if (MoveItemsFromGrid(*MainChest))
			{
				return true;
			}
			if (MoveItemsFromGrid(*SideChest))
			{
				return true;
			}
		}
	}
	// The chest was empty
	return false;
}





bool cHopperEntity::MoveItemsFromFurnace(cChunk & a_Chunk)
{
	auto furnace = static_cast<cFurnaceEntity *>(a_Chunk.GetBlockEntity(m_Pos.addedY(1)));
	if (furnace == nullptr)
	{
		FLOGWARNING("{0}: A furnace entity was not found where expected, at {1}", __FUNCTION__, m_Pos.addedY(1));
		return false;
	}

	// Try move from the output slot:
	if (MoveItemsFromSlot(*furnace, cFurnaceEntity::fsOutput))
	{
		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)
	{
		if (MoveItemsFromSlot(*furnace, cFurnaceEntity::fsFuel))
		{
			furnace->SetFuelSlot(cItem());
			return true;
		}
	}

	// Nothing can be moved
	return false;
}





bool cHopperEntity::MoveItemsFromGrid(cBlockEntityWithItems & a_Entity)
{
	auto & Grid = a_Entity.GetContents();
	int NumSlots = Grid.GetNumSlots();

	for (int i = 0; i < NumSlots; i++)
	{
		if (Grid.IsSlotEmpty(i))
		{
			continue;
		}
		if (MoveItemsFromSlot(a_Entity, i))
		{
			Grid.ChangeSlotCount(i, -1);
			return true;
		}
	}
	return false;
}





bool cHopperEntity::MoveItemsFromSlot(cBlockEntityWithItems & a_Entity, int a_SlotNum)
{
	cItem One(a_Entity.GetSlot(a_SlotNum).CopyOne());
	for (int i = 0; i < ContentsWidth * ContentsHeight; i++)
	{
		if (m_Contents.IsSlotEmpty(i))
		{
			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;
		}
		else if (m_Contents.GetSlot(i).IsEqual(One))
		{
			if (cPluginManager::Get()->CallHookHopperPullingItem(*m_World, *this, i, a_Entity, a_SlotNum))
			{
				// Plugin disagrees with the move
				continue;
			}

			auto PreviousCount = m_Contents.GetSlot(i).m_ItemCount;
			m_Contents.ChangeSlotCount(i, 1);

			if (PreviousCount + 1 == m_Contents.GetSlot(i).m_ItemCount)
			{
				// Successfully added a new item. (Failure condition consistutes: stack full)
				return true;
			}
		}
	}
	return false;
}





bool cHopperEntity::MoveItemsToChest(cChunk & a_Chunk, Vector3i a_Coords)
{
	// Try the chest directly connected to the hopper:
	auto ConnectedChest = static_cast<cChestEntity *>(a_Chunk.GetBlockEntity(a_Coords));
	if (ConnectedChest == nullptr)
	{
		FLOGWARNING("{0}: A chest entity was not found where expected, at {1}", __FUNCTION__, a_Coords);
		return false;
	}
	auto SideChest = ConnectedChest->GetNeighbour();
	if (SideChest == nullptr)
	{
		if (MoveItemsToGrid(*ConnectedChest))
		{
			return true;
		}
	}
	else
	{
		auto SideAbsCoords = SideChest->GetPos();
		if (SideAbsCoords.z > a_Coords.z || SideAbsCoords.x > a_Coords.x)
		{
			if (MoveItemsToGrid(*SideChest))
			{
				return true;
			}
			if (MoveItemsToGrid(*ConnectedChest))
			{
				return true;
			}
		}
		else
		{
			if (MoveItemsToGrid(*ConnectedChest))
			{
				return true;
			}
			if (MoveItemsToGrid(*SideChest))
			{
				return true;
			}
		}
	}
	return false;
}





bool cHopperEntity::MoveItemsToFurnace(cChunk & a_Chunk, Vector3i a_Coords, NIBBLETYPE a_HopperMeta)
{
	auto furnace = static_cast<cFurnaceEntity *>(a_Chunk.GetBlockEntity(a_Coords));
	if (a_HopperMeta == E_META_HOPPER_FACING_YM)
	{
		// Feed the input slot of the furnace
		return MoveItemsToSlot(*furnace, cFurnaceEntity::fsInput);
	}
	else
	{
		// Feed the fuel slot of the furnace
		return MoveItemsToSlot(*furnace, cFurnaceEntity::fsFuel);
	}
}





bool cHopperEntity::MoveItemsToGrid(cBlockEntityWithItems & a_Entity)
{
	// Iterate through our slots, try to move from each one:
	int NumSlots = a_Entity.GetContents().GetNumSlots();
	for (int i = 0; i < NumSlots; i++)
	{
		if (MoveItemsToSlot(a_Entity, i))
		{
			return true;
		}
	}
	return false;
}





bool cHopperEntity::MoveItemsToSlot(cBlockEntityWithItems & a_Entity, int a_DstSlotNum)
{
	cItemGrid & Grid = a_Entity.GetContents();
	if (Grid.IsSlotEmpty(a_DstSlotNum))
	{
		// 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))
			{
				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());
				m_Contents.ChangeSlotCount(i, -1);
				return true;
			}
		}
		return false;
	}
	else
	{
		// The slot is taken, try to top it up:
		const cItem & DestSlot = Grid.GetSlot(a_DstSlotNum);
		if (DestSlot.IsFullStack())
		{
			return false;
		}
		for (int i = 0; i < ContentsWidth * ContentsHeight; i++)
		{
			if (m_Contents.GetSlot(i).IsEqual(DestSlot))
			{
				if (cPluginManager::Get()->CallHookHopperPushingItem(*m_World, *this, i, a_Entity, a_DstSlotNum))
				{
					// A plugin disagrees with the move
					continue;
				}
				Grid.ChangeSlotCount(a_DstSlotNum, 1);
				m_Contents.ChangeSlotCount(i, -1);
				return true;
			}
		}
		return false;
	}
}