summaryrefslogblamecommitdiffstats
path: root/src/UI/SlotArea.cpp
blob: 94b059fb9cc334a0d09bcf10f5654f15ac4ca111 (plain) (tree)
1
2
3
4
5
6
7
8
9
 





                                                     
                               
                             
                                          
                                                

                                               
                                              
                                           
                                 
                                 
                        
                                

                               

                          
                        
                            




 
                                                                                














                                                                                                                   

                                                                                                



                                                                       
 


                                                                
                                                    
         
                                                                                      

                       
 







                                                                                                          
                              
         

                                       



                                                                         




                                                        




                                                           





                                                                                           












                                                                          



                              
         
 












                                                                                                                   
                                                                           
                         
                                                                                                                           
 
                                                                                                                                
                                                                             
 










                                                                                                                      
                                                                             
 
                                                                                    



                                                                        














                                                                  
 



















                                                                                                                             
                                                                                                                                                
 

                                                                    













                                                                                                                                   
 




                                                      









                                                                                                                      
                                                                              

                           
                                                                                                        


                                           
 







                                                                                                             

                                                             

                                             


                                                      


                                                                                                               
 





                                                                                            
 






                                                                                                                                  

















                                                                                        



























                                                                                






                                                                                            
                                                                     
















                                                                              

















                                                   
                                                                                                                                    


                                            


                                                                      
                                                                                          



                                          
                                                                                                   




                                             

                                                                   
                                  


                                                                         
                                                            












                                                  





                                                                                                    
                                                  

























                                                                                             
                                                                                






























                                                                                                  
                                                                                













































                                                                                                                               
                                                                                














                                                                                                                           





                                                   






                                                                                                



                                                                                          





                                                
 







                                                                          













                                                                                        



                                                           
 
















                                                                                                    
 













                                                                                        
                                                                                                                                            




                                 
                           




 

                                                         



                                                                
                                                  






                                                                
                                      

                                                

                                                  
         
                                              
         

                                                                                                
                 
                                                                       

                                                        

                                                          




                                                         
 















                                                                   
                


                                                                      
                                                                                     




                                                                  
 

                                                             
                                                                                    
 





                                                                                       


                                                                                                                               
 
                                                       

                                                        

                               
         





 




















                                                                



                                                                                   
                                                                              
                                                 














                                                                                                    
 


                                                                                   
                                                                              






                                                                            




















                                                                                                       
 
                                                                                

                  
                                                          
                                              
                         
                                      








                                                                                                                        








                                                                                  
                                                    
         
                                                                                      


                       
                              
         














                                                                         









                                                                       



                              

         


































                                                                                                                                                              
                               










                                                      
















                                                                                                                      
                                                                              





                                           
 







                                                                                                             
                                                                                                                                         


                                   


                                                                




                                                                                          
                                                                                                   




                                             

                                                                   



                                                                         
                                                            















                                                     


                                           
                                                                              









                                                                                          
                                                        










                                                            
                                                                                   

                          
                                                                                          

                             
                                                                                       




                                                                                  
                                                                                                             
         
                                                         





                                                        
                                                                                        
                                                                                                                                 



                                                    
                                                                                                                                       
                                                                                                                               



                 
                                                                                                                       










                                                          



                                                                                                               
                                                                                                                                                                                                   
          







                                                        
                                  











                                                     
 
                            





                                                           


                                        
                                            


                                   
                                                                                       
 
                                                       
                                                                                                                                  

                                                                                                
                                                                                                                                    
                                            











                                                                                 

                                                                                                                                        


                                    
                                                                           



                                                                        
                                                                                                        







                                                                           
                                                                         
                         


                                                                                                             
 







                                                                                                      
                                                                                               

                                                                             

                         


                                                                                         



                              
                                                                                                                   
                                     



                                                
                                                                                             



                                                 
                                                        

                                     
                                                                                     






                                                        
                                                      

         
                                             





                              
                                                                                       
         
                                   
         
                                                                    



                              

                             
                                                                                    

                                                
                                        
                 
                                                     
                                

                                                

                                    
                                                                                   





 













































                                                                                                                         
                                                    
         
                                                                                      





























































                                                                                                                   
 






























                                                               
                                                                                                                                          

















































                                                                                                           
 
                                                                                

                       
                                                                                                              
                                              


                           






 
                                                                                                                             
 


                                                                
                                                    
         
                                                                                      

                       
 







                                                                         

                                
                                                        

                               




                                                           


















                                                                          




                              
 








                                                                                                                   

                                   
         
                                                                        
                                    
                 
                                                      
                 


                                
                                                                                                  



                                                  
                 
                                             
                 
         
                                                                                
         
                                                                     
                                              
         


                                           
         
                                                      

         
 



 
                                                                                                                                        


                                                  
         
                       
         









                                                            



 
 



                                                           










                                                             

                                         





 









                                                                                          
                                                          
 

                                           
                                                                                   
         
                                                                                          
 

                                                                                                       


                                                                 
 


                                                                                        


            


                                                           






         
                                                              
 

                            
                                                                                                           
 


                                                                                
                                                                                                                  









































                                                                                                                                                                                   
                 
                 
                                      
                 

                                 
                           





 
                                                                                













                                                                                                      
                                                                      







                                                                                          
                                                                    





 
                                                                                























                                                                                                                          
                                 
         

                                                                              

                       



                                    
                                                            
                 
                                                                                              


                               







                                                                                                                           
 
                                      
                 























                                                                                                                


                                                                  

                                            



                                                        









                                                                                                   
                                                        












                                                                        
                                                            
                                                                        





                                                                      
                                                                                                                                        

                                                                                     
                                                                  


                                                             
                                                                                

















                                                                          





 
                                                                                                                                           
 



                                                                           
         




































                                                                                           





 

                                                                                
                         









                                                                                       
                         








                                                                           
                          

                                                                                          
 






                                              














                                                                                          
                                                                                

                                                                                                              
                                     


























                                                                                                                               
                                                    
         


                                                                                      
 


                                                                 
 


                                                 

                                      













                                                                                           


                                               





                                                                               
                         
                                
                         





                                                                                           
                         









                                                        
                         

                                                                               


                                




                                                                                               


                                      
         
 



                                                        
                 

                                               
                         





                                                                               
                                 
                                                                                         

                                               
                                      

                         








                                                                          
                                                                                             
 




                                                                                           



































































































                                                                                                                                                

































                                                                                                                

















































































































































































































                                                                                                                                                                    













                                                                                                                                
                                                                                                      
         




                                                                                           
 



                                                                                                                             
 



























                                                                                                                   
                                                                                

                  
                                                                                                                                         






































                                                                                                       



                                                                                                                        
                                                                                                                                 






                                                                                                      
 



                                                            
                            
                                                    
         
                                                                                      


                       
                              
         



















                                                                         

















                                                                                                                           
                                                                                   

















                                                      
                                                                             


                          
                                                                                                                      
                                                                              

                                                                            







                     
                                                                                

















































                                                                                        
                                                                                

















                                                                                                                            
 

                                                                                                                
         
 
                                                              


                                                                                            
                               
         
 
                                                              














                                                                                         
 
                                                              



                                                                                            
 
                                                             

                                                           








                                                                                                                                                    
                                                                                                                                      
























                                                                                                                              
 


                                             
                                                                   





                                              
 
                                      
                                                                           
                           
                                                                                                                                                                                        





 
                                                                    



                                                                      
                               
         
                                  




 
 

// SlotArea.cpp

// Implements the cSlotArea class and its descendants

#include "Globals.h"
#include "SlotArea.h"
#include "../Entities/Player.h"
#include "../Mobs/Villager.h"
#include "../BlockEntities/BeaconEntity.h"
#include "../BlockEntities/BrewingstandEntity.h"
#include "../BlockEntities/ChestEntity.h"
#include "../BlockEntities/DropSpenserEntity.h"
#include "../BlockEntities/EnderChestEntity.h"
#include "../BlockEntities/FurnaceEntity.h"
#include "../Entities/Minecart.h"
#include "../Items/ItemHandler.h"
#include "AnvilWindow.h"
#include "VillagerTradeWindow.h"
#include "../CraftingRecipes.h"
#include "../Root.h"
#include "../FastRandom.h"
#include "../BlockArea.h"
#include "../EffectID.h"
#include "../ClientHandle.h"





////////////////////////////////////////////////////////////////////////////////
// cSlotArea:

cSlotArea::cSlotArea(int a_NumSlots, cWindow & a_ParentWindow) :
	m_NumSlots(a_NumSlots),
	m_ParentWindow(a_ParentWindow)
{
}





void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	/*
	LOGD("Slot area with %d slots clicked at slot number %d, clicked item %s, slot item %s",
		GetNumSlots(), a_SlotNum,
		ItemToFullString(a_ClickedItem).c_str(),
		ItemToFullString(*GetSlot(a_SlotNum, a_Player)).c_str()
	);
	*/

	ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));

	bool bAsync = false;
	if (GetSlot(a_SlotNum, a_Player) == nullptr)
	{
		LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
		return;
	}

	if (a_Player.IsGameModeSpectator())
	{
		// Block the action of the player and make sure, the inventory doesn't get out of sync
		a_Player.GetClientHandle()->SendInventorySlot(-1, -1, cItem());  // Reset the dragged item
		SetSlot(a_SlotNum, a_Player, *GetSlot(a_SlotNum, a_Player));  // Update the current slot
		return;
	}

	switch (a_ClickAction)
	{
		case caShiftLeftClick:
		case caShiftRightClick:
		{
			ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
			return;
		}
		case caDblClick:
		{
			DblClicked(a_Player, a_SlotNum);
			return;
		}
		case caMiddleClick:
		{
			MiddleClicked(a_Player, a_SlotNum);
			return;
		}
		case caDropKey:
		case caCtrlDropKey:
		{
			DropClicked(a_Player, a_SlotNum, (a_ClickAction == caCtrlDropKey));
			return;
		}
		case caNumber1:
		case caNumber2:
		case caNumber3:
		case caNumber4:
		case caNumber5:
		case caNumber6:
		case caNumber7:
		case caNumber8:
		case caNumber9:
		{
			NumberClicked(a_Player, a_SlotNum, a_ClickAction);
			return;
		}
		default:
		{
			break;
		}
	}

	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	if (!Slot.IsSameType(a_ClickedItem))
	{
		LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
		LOGWARNING("My item:    %s", ItemToFullString(Slot).c_str());
		LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
		bAsync = true;
	}
	cItem & DraggingItem = a_Player.GetDraggingItem();
	switch (a_ClickAction)
	{
		case caRightClick:
		{
			if (DraggingItem.m_ItemType <= 0)  // Empty-handed?
			{
				DraggingItem = Slot.CopyOne();  // Obtain copy of slot to preserve lore, enchantments, etc.

				DraggingItem.m_ItemCount = static_cast<char>(static_cast<float>(Slot.m_ItemCount) / 2.f + 0.5f);
				Slot.m_ItemCount -= DraggingItem.m_ItemCount;

				if (Slot.m_ItemCount <= 0)
				{
					Slot.Empty();
				}
			}
			else if ((Slot.m_ItemType <= 0) || DraggingItem.IsEqual(Slot))
			{
				// Drop one item in slot
				cItemHandler * Handler = ItemHandler(Slot.m_ItemType);
				if ((DraggingItem.m_ItemCount > 0) && (Slot.m_ItemCount < Handler->GetMaxStackSize()))
				{
					char OldSlotCount = Slot.m_ItemCount;

					Slot = DraggingItem.CopyOne();  // See above
					OldSlotCount++;
					Slot.m_ItemCount = OldSlotCount;

					DraggingItem.m_ItemCount--;
				}
				if (DraggingItem.m_ItemCount <= 0)
				{
					DraggingItem.Empty();
				}
			}
			else if (!DraggingItem.IsEqual(Slot))
			{
				// Swap contents
				cItem tmp(DraggingItem);
				DraggingItem = Slot;
				Slot = tmp;
			}
			break;
		}

		case caLeftClick:
		{
			// Left-clicked
			if (!DraggingItem.IsEqual(Slot))
			{
				// Switch contents
				cItem tmp(DraggingItem);
				DraggingItem = Slot;
				Slot = tmp;
			}
			else
			{
				// Same type, add items:
				cItemHandler * Handler = ItemHandler(DraggingItem.m_ItemType);
				int FreeSlots = Handler->GetMaxStackSize() - Slot.m_ItemCount;
				if (FreeSlots < 0)
				{
					ASSERT(!"Bad item stack size - where did we get more items in a slot than allowed?");
					FreeSlots = 0;
				}
				char Filling = static_cast<char>((FreeSlots > DraggingItem.m_ItemCount) ? DraggingItem.m_ItemCount : FreeSlots);

				Slot.m_ItemCount += Filling;
				DraggingItem.m_ItemCount -= Filling;
				if (DraggingItem.m_ItemCount <= 0)
				{
					DraggingItem.Empty();
				}
			}
			break;
		}
		default:
		{
			LOGWARNING("SlotArea: Unhandled click action: %d (%s)", a_ClickAction, ClickActionToString(a_ClickAction));
			m_ParentWindow.BroadcastWholeWindow();
			return;
		}
	}  // switch (a_ClickAction

	SetSlot(a_SlotNum, a_Player, Slot);
	if (bAsync)
	{
		m_ParentWindow.BroadcastWholeWindow();
	}
}





void cSlotArea::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem)
{
	// Make a copy of the slot, distribute it among the other areas, then update the slot to contain the leftover:
	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	m_ParentWindow.DistributeStack(Slot, a_SlotNum, a_Player, this, true);
	if (Slot.IsEmpty())
	{
		// Empty the slot completely, the client doesn't like left-over ItemType with zero count
		Slot.Empty();
	}
	SetSlot(a_SlotNum, a_Player, Slot);

	// Some clients try to guess our actions and not always right (armor slots in 1.2.5), so we fix them:
	m_ParentWindow.BroadcastWholeWindow();
}





void cSlotArea::DblClicked(cPlayer & a_Player, int a_SlotNum)
{
	// Assume single-click was sent first

	cItem & Dragging = a_Player.GetDraggingItem();
	if (Dragging.IsEmpty())
	{
		LOGD("%s DblClicked with an empty hand over empty slot, ignoring", a_Player.GetName().c_str());
		return;
	}

	// Add as many items from the surrounding area into hand as possible:
	// First skip full stacks, then if there's still space, process full stacks as well:
	if (!m_ParentWindow.CollectItemsToHand(Dragging, *this, a_Player, false))
	{
		m_ParentWindow.CollectItemsToHand(Dragging, *this, a_Player, true);
	}

	m_ParentWindow.BroadcastWholeWindow();  // We need to broadcast, in case the window was a chest opened by multiple players
}





void cSlotArea::MiddleClicked(cPlayer & a_Player, int a_SlotNum)
{
	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	cItem & DraggingItem = a_Player.GetDraggingItem();

	if (!a_Player.IsGameModeCreative() || Slot.IsEmpty() || !DraggingItem.IsEmpty())
	{
		return;
	}

	DraggingItem = Slot;
	DraggingItem.m_ItemCount = DraggingItem.GetMaxStackSize();
}





void cSlotArea::DropClicked(cPlayer & a_Player, int a_SlotNum, bool a_DropStack)
{
	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	if (Slot.IsEmpty())
	{
		return;
	}

	cItem ItemToDrop = Slot.CopyOne();
	if (a_DropStack)
	{
		ItemToDrop.m_ItemCount = Slot.m_ItemCount;
	}

	Slot.m_ItemCount -= ItemToDrop.m_ItemCount;
	if (Slot.m_ItemCount <= 0)
	{
		Slot.Empty();
	}
	SetSlot(a_SlotNum, a_Player, Slot);

	a_Player.TossPickup(ItemToDrop);
}





void cSlotArea::NumberClicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction)
{
	if ((a_ClickAction < caNumber1) || (a_ClickAction > caNumber9))
	{
		return;
	}

	int HotbarSlot = static_cast<int>(a_ClickAction - caNumber1);
	cItem ItemInHotbar(a_Player.GetInventory().GetHotbarSlot(HotbarSlot));
	cItem ItemInSlot(*GetSlot(a_SlotNum, a_Player));

	// The items are equal. Do nothing.
	if (ItemInHotbar.IsEqual(ItemInSlot))
	{
		return;
	}

	a_Player.GetInventory().SetHotbarSlot(HotbarSlot, ItemInSlot);
	SetSlot(a_SlotNum, a_Player, ItemInHotbar);
}





void cSlotArea::OnPlayerAdded(cPlayer & a_Player)
{
	UNUSED(a_Player);
}





void cSlotArea::OnPlayerRemoved(cPlayer & a_Player)
{
	UNUSED(a_Player);
}





void cSlotArea::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
	for (int i = 0; i < m_NumSlots; i++)
	{
		int SlotNum = (a_BackFill) ? (m_NumSlots - 1 - i) : i;

		const cItem * Slot = GetSlot(SlotNum, a_Player);
		if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots))
		{
			// Different items
			continue;
		}
		char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
		if (NumFit <= 0)
		{
			// Full stack already
			continue;
		}
		NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);

		if (a_ShouldApply)
		{
			cItem NewSlot(a_ItemStack);
			NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
			SetSlot(SlotNum, a_Player, NewSlot);
		}
		a_ItemStack.m_ItemCount -= NumFit;
		if (a_ItemStack.IsEmpty())
		{
			return;
		}
	}  // for i - Slots
}





bool cSlotArea::CollectItemsToHand(cItem & a_Dragging, cPlayer & a_Player, bool a_CollectFullStacks)
{
	int NumSlots = GetNumSlots();
	for (int i = 0; i < NumSlots; i++)
	{
		const cItem & SlotItem = *GetSlot(i, a_Player);
		if (!SlotItem.IsEqual(a_Dragging))
		{
			continue;
		}
		int ToMove = a_Dragging.GetMaxStackSize() - a_Dragging.m_ItemCount;
		if (ToMove > SlotItem.m_ItemCount)
		{
			ToMove = SlotItem.m_ItemCount;
		}
		a_Dragging.m_ItemCount += ToMove;
		cItem NewSlot(SlotItem);
		NewSlot.m_ItemCount -= ToMove;
		SetSlot(i, a_Player, NewSlot);
		if (!NewSlot.IsEmpty())
		{
			// There are leftovers in the slot, so a_Dragging must be full
			return true;
		}
	}  // for i - Slots[]
	// a_Dragging may be full if there were exactly the number of items needed to fill it
	return a_Dragging.IsFullStack();
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaChest:

cSlotAreaChest::cSlotAreaChest(cChestEntity * a_Chest, cWindow & a_ParentWindow) :
	cSlotArea(27, a_ParentWindow),
	m_Chest(a_Chest)
{
}





const cItem * cSlotAreaChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	// a_SlotNum ranges from 0 to 26, use that to index the chest entity's inventory directly:
	return &(m_Chest->GetSlot(a_SlotNum));
}





void cSlotAreaChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	m_Chest->SetSlot(a_SlotNum, a_Item);
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaDoubleChest:

cSlotAreaDoubleChest::cSlotAreaDoubleChest(cChestEntity * a_TopChest, cChestEntity * a_BottomChest, cWindow & a_ParentWindow) :
	cSlotArea(54, a_ParentWindow),
	m_TopChest(a_TopChest),
	m_BottomChest(a_BottomChest)
{
}





const cItem * cSlotAreaDoubleChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	// a_SlotNum ranges from 0 to 53, use that to index the correct chest's inventory:
	if (a_SlotNum < 27)
	{
		return &(m_TopChest->GetSlot(a_SlotNum));
	}
	else
	{
		return &(m_BottomChest->GetSlot(a_SlotNum - 27));
	}
}





void cSlotAreaDoubleChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	if (a_SlotNum < 27)
	{
		m_TopChest->SetSlot(a_SlotNum, a_Item);
	}
	else
	{
		m_BottomChest->SetSlot(a_SlotNum - 27, a_Item);
	}
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaCrafting:

cSlotAreaCrafting::cSlotAreaCrafting(int a_GridSize, cWindow & a_ParentWindow) :
	cSlotAreaTemporary(1 + a_GridSize * a_GridSize, a_ParentWindow),
	m_GridSize(a_GridSize)
{
	ASSERT((a_GridSize == 2) || (a_GridSize == 3));
}





void cSlotAreaCrafting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	if (a_ClickAction == caMiddleClick)
	{
		MiddleClicked(a_Player, a_SlotNum);
		return;
	}

	// Override for craft result slot
	if (a_SlotNum == 0)
	{
		if ((a_ClickAction == caShiftLeftClick) || (a_ClickAction == caShiftRightClick))
		{
			ShiftClickedResult(a_Player);
		}
		else if ((a_ClickAction == caDropKey) || (a_ClickAction == caCtrlDropKey))
		{
			DropClickedResult(a_Player);
		}
		else
		{
			ClickedResult(a_Player);
		}
		return;
	}

	super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
	UpdateRecipe(a_Player);
}





void cSlotAreaCrafting::DblClicked(cPlayer & a_Player, int a_SlotNum)
{
	if (a_SlotNum == 0)
	{
		// Dbl-clicking the crafting result slot shouldn't collect items to hand
		return;
	}
	super::DblClicked(a_Player, a_SlotNum);
}





void cSlotAreaCrafting::OnPlayerRemoved(cPlayer & a_Player)
{
	// Toss all items on the crafting grid:
	TossItems(a_Player, 1, m_NumSlots);

	// Remove the current recipe from the player -> recipe map:
	for (cRecipeMap::iterator itr = m_Recipes.begin(), end = m_Recipes.end(); itr != end; ++itr)
	{
		if (itr->first == a_Player.GetUniqueID())
		{
			// Remove the player from the recipe map:
			m_Recipes.erase(itr);
			return;
		}
	}  // for itr - m_Recipes[]
	// Player not found - that is acceptable
}






void cSlotAreaCrafting::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	// Update the recipe after setting the slot, if the slot is not the result slot:
	super::SetSlot(a_SlotNum, a_Player, a_Item);
	if (a_SlotNum != 0)
	{
		UpdateRecipe(a_Player);
	}
}





void cSlotAreaCrafting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
	UNUSED(a_ItemStack);
	UNUSED(a_Player);
	UNUSED(a_ShouldApply);
	UNUSED(a_KeepEmptySlots);
	UNUSED(a_BackFill);
}




void cSlotAreaCrafting::ClickedResult(cPlayer & a_Player)
{
	cItem & DraggingItem = a_Player.GetDraggingItem();

	// Get the current recipe:
	cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
	const cItem & Result = Recipe.GetResult();

	cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1;
	cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize);

	// If possible, craft:
	if (DraggingItem.IsEmpty())
	{
		DraggingItem = Result;
		Recipe.ConsumeIngredients(Grid);
		Grid.CopyToItems(PlayerSlots);

		HandleCraftItem(Result, a_Player);
	}
	else if (DraggingItem.IsEqual(Result))
	{
		cItemHandler * Handler = ItemHandler(Result.m_ItemType);
		if (DraggingItem.m_ItemCount + Result.m_ItemCount <= Handler->GetMaxStackSize())
		{
			DraggingItem.m_ItemCount += Result.m_ItemCount;
			Recipe.ConsumeIngredients(Grid);
			Grid.CopyToItems(PlayerSlots);

			HandleCraftItem(Result, a_Player);
		}
	}

	// Get the new recipe and update the result slot:
	UpdateRecipe(a_Player);

	// We're done. Send all changes to the client and bail out:
	m_ParentWindow.BroadcastWholeWindow();
}





void cSlotAreaCrafting::ShiftClickedResult(cPlayer & a_Player)
{
	cItem Result(*GetSlot(0, a_Player));
	if (Result.IsEmpty())
	{
		return;
	}
	cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1;
	for (;;)
	{
		// Try distributing the result. If it fails, bail out:
		cItem ResultCopy(Result);
		m_ParentWindow.DistributeStack(ResultCopy, 0, a_Player, this, false);
		if (!ResultCopy.IsEmpty())
		{
			// Couldn't distribute all of it. Bail out
			return;
		}

		// Distribute the result, this time for real:
		ResultCopy = Result;
		m_ParentWindow.DistributeStack(ResultCopy, 0, a_Player, this, true);

		// Remove the ingredients from the crafting grid and update the recipe:
		cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
		cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize);
		Recipe.ConsumeIngredients(Grid);
		Grid.CopyToItems(PlayerSlots);
		UpdateRecipe(a_Player);

		// Broadcast the window, we sometimes move items to different locations than Vanilla, causing needless desyncs:
		m_ParentWindow.BroadcastWholeWindow();

		// If the recipe has changed, bail out:
		if (!Recipe.GetResult().IsEqual(Result))
		{
			return;
		}
	}
}





void cSlotAreaCrafting::DropClickedResult(cPlayer & a_Player)
{
	// Get the current recipe:
	cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
	const cItem & Result = Recipe.GetResult();

	cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1;
	cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize);

	a_Player.TossPickup(Result);
	Recipe.ConsumeIngredients(Grid);
	Grid.CopyToItems(PlayerSlots);

	HandleCraftItem(Result, a_Player);
	UpdateRecipe(a_Player);
}





void cSlotAreaCrafting::UpdateRecipe(cPlayer & a_Player)
{
	cCraftingGrid   Grid(GetPlayerSlots(a_Player) + 1, m_GridSize, m_GridSize);
	cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
	cRoot::Get()->GetCraftingRecipes()->GetRecipe(a_Player, Grid, Recipe);
	SetSlot(0, a_Player, Recipe.GetResult());
}





cCraftingRecipe & cSlotAreaCrafting::GetRecipeForPlayer(cPlayer & a_Player)
{
	for (cRecipeMap::iterator itr = m_Recipes.begin(), end = m_Recipes.end(); itr != end; ++itr)
	{
		if (itr->first == a_Player.GetUniqueID())
		{
			return itr->second;
		}
	}  // for itr - m_Recipes[]

	// Not found. Add a new one:
	cCraftingGrid   Grid(GetPlayerSlots(a_Player) + 1, m_GridSize, m_GridSize);
	cCraftingRecipe Recipe(Grid);
	cRoot::Get()->GetCraftingRecipes()->GetRecipe(a_Player, Grid, Recipe);
	m_Recipes.push_back(std::make_pair(a_Player.GetUniqueID(), Recipe));
	return m_Recipes.back().second;
}




void cSlotAreaCrafting::HandleCraftItem(const cItem & a_Result, cPlayer & a_Player)
{
	switch (a_Result.m_ItemType)
	{
		case E_BLOCK_WORKBENCH:         a_Player.AwardAchievement(achCraftWorkbench);    break;
		case E_BLOCK_FURNACE:           a_Player.AwardAchievement(achCraftFurnace);      break;
		case E_BLOCK_CAKE:              a_Player.AwardAchievement(achBakeCake);          break;
		case E_BLOCK_ENCHANTMENT_TABLE: a_Player.AwardAchievement(achCraftEnchantTable); break;
		case E_BLOCK_BOOKCASE:          a_Player.AwardAchievement(achBookshelf);         break;
		case E_ITEM_WOODEN_PICKAXE:     a_Player.AwardAchievement(achCraftPickaxe);      break;
		case E_ITEM_WOODEN_SWORD:       a_Player.AwardAchievement(achCraftSword);        break;
		case E_ITEM_STONE_PICKAXE:      a_Player.AwardAchievement(achCraftBetterPick);   break;
		case E_ITEM_WOODEN_HOE:         a_Player.AwardAchievement(achCraftHoe);          break;
		case E_ITEM_BREAD:              a_Player.AwardAchievement(achMakeBread);         break;
		default: break;
	}
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaAnvil:

cSlotAreaAnvil::cSlotAreaAnvil(cWindow & a_ParentWindow) :
	cSlotAreaTemporary(3, a_ParentWindow),
	m_MaximumCost(0),
	m_StackSizeToBeUsedInRepair(0)
{
}





void cSlotAreaAnvil::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));
	if (a_SlotNum != 2)
	{
		super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
		UpdateResult(a_Player);
		return;
	}

	bool bAsync = false;
	if (GetSlot(a_SlotNum, a_Player) == nullptr)
	{
		LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
		return;
	}

	switch (a_ClickAction)
	{
		case caDblClick:
		{
			return;
		}
		case caShiftLeftClick:
		case caShiftRightClick:
		{
			ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
			return;
		}
		case caMiddleClick:
		{
			MiddleClicked(a_Player, a_SlotNum);
			return;
		}
		case caDropKey:
		case caCtrlDropKey:
		{
			if (CanTakeResultItem(a_Player))
			{
				DropClicked(a_Player, a_SlotNum, true);
				OnTakeResult(a_Player);
			}
			return;
		}
		default:
		{
			break;
		}
	}

	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	if (!Slot.IsSameType(a_ClickedItem))
	{
		LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
		LOGWARNING("My item:    %s", ItemToFullString(Slot).c_str());
		LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
		bAsync = true;
	}
	cItem & DraggingItem = a_Player.GetDraggingItem();

	if (Slot.IsEmpty())
	{
		return;
	}
	if (!DraggingItem.IsEmpty())
	{
		if (!(DraggingItem.IsEqual(Slot) && ((DraggingItem.m_ItemCount + Slot.m_ItemCount) <= cItemHandler::GetItemHandler(Slot)->GetMaxStackSize())))
		{
			return;
		}
	}

	if (!CanTakeResultItem(a_Player))
	{
		return;
	}

	cItem NewItem = cItem(Slot);
	NewItem.m_ItemCount += DraggingItem.m_ItemCount;

	Slot.Empty();
	DraggingItem.Empty();
	SetSlot(a_SlotNum, a_Player, Slot);

	DraggingItem = NewItem;
	OnTakeResult(a_Player);

	if (bAsync)
	{
		m_ParentWindow.BroadcastWholeWindow();
	}
}





void cSlotAreaAnvil::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem)
{
	if (a_SlotNum != 2)
	{
		super::ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
		UpdateResult(a_Player);
		return;
	}

	// Make a copy of the slot, distribute it among the other areas, then update the slot to contain the leftover:
	cItem Slot(*GetSlot(a_SlotNum, a_Player));

	if (Slot.IsEmpty() || !CanTakeResultItem(a_Player))
	{
		return;
	}

	m_ParentWindow.DistributeStack(Slot, a_SlotNum, a_Player, this, true);
	if (Slot.IsEmpty())
	{
		Slot.Empty();
		OnTakeResult(a_Player);
	}
	SetSlot(a_SlotNum, a_Player, Slot);

	// Some clients try to guess our actions and not always right (armor slots in 1.2.5), so we fix them:
	m_ParentWindow.BroadcastWholeWindow();
}





void cSlotAreaAnvil::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
	for (int i = 0; i < 2; i++)
	{
		int SlotNum = (a_BackFill) ? (2 - 1 - i) : i;

		const cItem * Slot = GetSlot(SlotNum, a_Player);
		if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots))
		{
			// Different items
			continue;
		}
		char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
		if (NumFit <= 0)
		{
			// Full stack already
			continue;
		}
		NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);

		if (a_ShouldApply)
		{
			cItem NewSlot(a_ItemStack);
			NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
			SetSlot(SlotNum, a_Player, NewSlot);
		}
		a_ItemStack.m_ItemCount -= NumFit;
		if (a_ItemStack.IsEmpty())
		{
			UpdateResult(a_Player);
			return;
		}
	}  // for i - Slots
	UpdateResult(a_Player);
}





void cSlotAreaAnvil::OnTakeResult(cPlayer & a_Player)
{
	if (!a_Player.IsGameModeCreative())
	{
		a_Player.DeltaExperience(-cPlayer::XpForLevel(m_MaximumCost));
	}
	SetSlot(0, a_Player, cItem());

	if (m_StackSizeToBeUsedInRepair > 0)
	{
		const cItem * Item = GetSlot(1, a_Player);
		if (!Item->IsEmpty() && (Item->m_ItemCount > m_StackSizeToBeUsedInRepair))
		{
			cItem NewSecondItem(*Item);
			NewSecondItem.m_ItemCount -= m_StackSizeToBeUsedInRepair;
			m_StackSizeToBeUsedInRepair = 0;
			SetSlot(1, a_Player, NewSecondItem);
		}
		else
		{
			SetSlot(1, a_Player, cItem());
		}
	}
	else
	{
		SetSlot(1, a_Player, cItem());
	}
	m_ParentWindow.SetProperty(0, static_cast<short>(m_MaximumCost), a_Player);

	m_MaximumCost = 0;
	reinterpret_cast<cAnvilWindow &>(m_ParentWindow).SetRepairedItemName("", nullptr);

	int PosX, PosY, PosZ;
	reinterpret_cast<cAnvilWindow &>(m_ParentWindow).GetBlockPos(PosX, PosY, PosZ);

	BLOCKTYPE Block;
	NIBBLETYPE BlockMeta;
	a_Player.GetWorld()->GetBlockTypeMeta(PosX, PosY, PosZ, Block, BlockMeta);

	if (!a_Player.IsGameModeCreative() && (Block == E_BLOCK_ANVIL) && GetRandomProvider().RandBool(0.12))
	{
		NIBBLETYPE Orientation = BlockMeta & 0x3;
		NIBBLETYPE AnvilDamage = BlockMeta >> 2;
		++AnvilDamage;

		if (AnvilDamage > 2)
		{
			// Anvil will break
			a_Player.GetWorld()->SetBlock(PosX, PosY, PosZ, E_BLOCK_AIR, 0);
			a_Player.GetWorld()->BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_ANVIL_BREAK, PosX, PosY, PosZ, 0);
			a_Player.CloseWindow(false);
		}
		else
		{
			a_Player.GetWorld()->SetBlockMeta(PosX, PosY, PosZ, static_cast<NIBBLETYPE>(Orientation | (AnvilDamage << 2)));
			a_Player.GetWorld()->BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_ANVIL_USE, PosX, PosY, PosZ, 0);
		}
	}
	else
	{
		a_Player.GetWorld()->BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_ANVIL_USE, PosX, PosY, PosZ, 0);
	}
}





bool cSlotAreaAnvil::CanTakeResultItem(cPlayer & a_Player)
{
	return (
			(
				a_Player.IsGameModeCreative() ||              // Is the player in gamemode?
				(a_Player.GetXpLevel() >= m_MaximumCost)      // or the player have enough exp?
			) &&
			(!GetSlot(2, a_Player)->IsEmpty()) &&             // Is a item in the result slot?
			(m_MaximumCost > 0)                               // When no maximum cost is set, the item isn't set from the UpdateResult() method and can't be a valid enchanting result.
	);
}





void cSlotAreaAnvil::OnPlayerRemoved(cPlayer & a_Player)
{
	TossItems(a_Player, 0, 2);
	super::OnPlayerRemoved(a_Player);
}





void cSlotAreaAnvil::UpdateResult(cPlayer & a_Player)
{
	cItem Input(*GetSlot(0, a_Player));
	cItem SecondInput(*GetSlot(1, a_Player));
	cItem Output(*GetSlot(2, a_Player));

	if (Input.IsEmpty())
	{
		Output.Empty();
		SetSlot(2, a_Player, Output);
		m_ParentWindow.SetProperty(0, 0, a_Player);
		return;
	}

	m_MaximumCost = 0;
	m_StackSizeToBeUsedInRepair = 0;
	int RepairCost = Input.m_RepairCost;
	int NeedExp = 0;
	if (!SecondInput.IsEmpty())
	{
		bool IsEnchantBook = (SecondInput.m_ItemType == E_ITEM_ENCHANTED_BOOK);

		RepairCost += SecondInput.m_RepairCost;
		if (Input.IsDamageable() && cItemHandler::GetItemHandler(Input)->CanRepairWithRawMaterial(SecondInput.m_ItemType))
		{
			// Tool and armor repair with special item (iron / gold / diamond / ...)
			int DamageDiff = std::min(static_cast<int>(Input.m_ItemDamage), static_cast<int>(Input.GetMaxDamage()) / 4);
			if (DamageDiff <= 0)
			{
				// No enchantment
				Output.Empty();
				SetSlot(2, a_Player, Output);
				m_ParentWindow.SetProperty(0, 0, a_Player);
				return;
			}

			int x = 0;
			while ((DamageDiff > 0) && (x < SecondInput.m_ItemCount))
			{
				Input.m_ItemDamage -= DamageDiff;
				NeedExp += std::max(1, DamageDiff / 100) + static_cast<int>(Input.m_Enchantments.Count());
				DamageDiff = std::min(static_cast<int>(Input.m_ItemDamage), static_cast<int>(Input.GetMaxDamage()) / 4);

				++x;
			}
			m_StackSizeToBeUsedInRepair = static_cast<char>(x);
		}
		else
		{
			// Tool and armor repair with two tools / armors
			if (!IsEnchantBook && (!Input.IsSameType(SecondInput) || !Input.IsDamageable()))
			{
				// No enchantment
				Output.Empty();
				SetSlot(2, a_Player, Output);
				m_ParentWindow.SetProperty(0, 0, a_Player);
				return;
			}

			if ((Input.GetMaxDamage() > 0) && !IsEnchantBook)
			{
				int FirstDamageDiff = Input.GetMaxDamage() - Input.m_ItemDamage;
				int SecondDamageDiff = SecondInput.GetMaxDamage() - SecondInput.m_ItemDamage;
				int Damage = SecondDamageDiff + Input.GetMaxDamage() * 12 / 100;

				int NewItemDamage = Input.GetMaxDamage() - (FirstDamageDiff + Damage);
				if (NewItemDamage > 0)
				{
					NewItemDamage = 0;
				}

				if (NewItemDamage < Input.m_ItemDamage)
				{
					Input.m_ItemDamage = static_cast<short>(NewItemDamage);
					NeedExp += std::max(1, Damage / 100);
				}
			}

			// Add the enchantments from the sacrifice to the target
			int EnchantmentCost = Input.AddEnchantmentsFromItem(SecondInput);
			NeedExp += EnchantmentCost;
		}
	}

	int NameChangeExp = 0;
	const AString & RepairedItemName = reinterpret_cast<cAnvilWindow*>(&m_ParentWindow)->GetRepairedItemName();
	if (RepairedItemName.empty())
	{
		// Remove custom name
		if (!Input.m_CustomName.empty())
		{
			NameChangeExp = (Input.IsDamageable()) ? 7 : (Input.m_ItemCount * 5);
			NeedExp += NameChangeExp;
			Input.m_CustomName = "";
		}
	}
	else if (RepairedItemName != Input.m_CustomName)
	{
		// Change custom name
		NameChangeExp = (Input.IsDamageable()) ? 7 : (Input.m_ItemCount * 5);
		NeedExp += NameChangeExp;

		if (!Input.m_CustomName.empty())
		{
			RepairCost += NameChangeExp / 2;
		}

		Input.m_CustomName = RepairedItemName;
	}

	m_MaximumCost = RepairCost + NeedExp;

	if (NeedExp < 0)
	{
		Input.Empty();
	}

	if ((NameChangeExp == NeedExp) && (NameChangeExp > 0) && (m_MaximumCost >= 40))
	{
		m_MaximumCost = 39;
	}
	if ((m_MaximumCost >= 40) && !a_Player.IsGameModeCreative())
	{
		Input.Empty();
	}

	if (!Input.IsEmpty())
	{
		RepairCost = std::max(Input.m_RepairCost, SecondInput.m_RepairCost);
		if (!Input.m_CustomName.empty())
		{
			RepairCost -= 9;
		}
		RepairCost = std::max(RepairCost, 0);
		RepairCost += 2;
		Input.m_RepairCost = RepairCost;
	}

	SetSlot(2, a_Player, Input);
	m_ParentWindow.SetProperty(0, static_cast<Int16>(m_MaximumCost), a_Player);
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaBeacon:

cSlotAreaBeacon::cSlotAreaBeacon(cBeaconEntity * a_Beacon, cWindow & a_ParentWindow) :
	cSlotArea(1, a_ParentWindow),
	m_Beacon(a_Beacon)
{
	m_Beacon->GetContents().AddListener(*this);
}





cSlotAreaBeacon::~cSlotAreaBeacon()
{
	m_Beacon->GetContents().RemoveListener(*this);
}




bool cSlotAreaBeacon::IsPlaceableItem(short a_ItemType)
{
	switch (a_ItemType)
	{
		case E_ITEM_EMERALD:
		case E_ITEM_DIAMOND:
		case E_ITEM_GOLD:
		case E_ITEM_IRON:
		{
			return true;
		}
	}
	return false;
}





void cSlotAreaBeacon::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));

	bool bAsync = false;
	if (GetSlot(a_SlotNum, a_Player) == nullptr)
	{
		LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
		return;
	}

	switch (a_ClickAction)
	{
		case caShiftLeftClick:
		case caShiftRightClick:
		{
			ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
			return;
		}
		case caMiddleClick:
		{
			MiddleClicked(a_Player, a_SlotNum);
			return;
		}
		case caDropKey:
		case caCtrlDropKey:
		{
			DropClicked(a_Player, a_SlotNum, false);
			return;
		}
		case caNumber1:
		case caNumber2:
		case caNumber3:
		case caNumber4:
		case caNumber5:
		case caNumber6:
		case caNumber7:
		case caNumber8:
		case caNumber9:
		{
			NumberClicked(a_Player, a_SlotNum, a_ClickAction);
			return;
		}
		default:
		{
			break;
		}
	}

	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	if (!Slot.IsSameType(a_ClickedItem))
	{
		LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
		LOGWARNING("My item:    %s", ItemToFullString(Slot).c_str());
		LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
		bAsync = true;
	}
	cItem & DraggingItem = a_Player.GetDraggingItem();

	if (DraggingItem.IsEmpty())
	{
		DraggingItem = Slot;
		Slot.Empty();
	}
	else if (Slot.IsEmpty())
	{
		if (!IsPlaceableItem(DraggingItem.m_ItemType))
		{
			return;
		}

		Slot = DraggingItem.CopyOne();
		DraggingItem.m_ItemCount -= 1;
		if (DraggingItem.m_ItemCount <= 0)
		{
			DraggingItem.Empty();
		}
	}
	else if (DraggingItem.m_ItemCount == 1)
	{
		if (!IsPlaceableItem(DraggingItem.m_ItemCount))
		{
			return;
		}

		// Switch contents
		cItem tmp(DraggingItem);
		DraggingItem = Slot;
		Slot = tmp;
	}

	SetSlot(a_SlotNum, a_Player, Slot);
	if (bAsync)
	{
		m_ParentWindow.BroadcastWholeWindow();
	}
}





void cSlotAreaBeacon::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
	const cItem * Slot = GetSlot(0, a_Player);
	if (!Slot->IsEmpty() || !IsPlaceableItem(a_ItemStack.m_ItemType) || (a_ItemStack.m_ItemCount != 1))
	{
		return;
	}

	if (a_ShouldApply)
	{
		SetSlot(0, a_Player, a_ItemStack.CopyOne());
	}
	a_ItemStack.Empty();
}





const cItem * cSlotAreaBeacon::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	UNUSED(a_Player);
	return &(m_Beacon->GetSlot(a_SlotNum));
}





void cSlotAreaBeacon::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	UNUSED(a_Player);
	m_Beacon->SetSlot(a_SlotNum, a_Item);
}





void cSlotAreaBeacon::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
	UNUSED(a_SlotNum);
	// Something has changed in the window, broadcast the entire window to all clients
	ASSERT(a_ItemGrid == &(m_Beacon->GetContents()));

	m_ParentWindow.BroadcastWholeWindow();
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaEnchanting:

cSlotAreaEnchanting::cSlotAreaEnchanting(cWindow & a_ParentWindow, int a_BlockX, int a_BlockY, int a_BlockZ) :
	cSlotAreaTemporary(2, a_ParentWindow),
	m_BlockX(a_BlockX),
	m_BlockY(a_BlockY),
	m_BlockZ(a_BlockZ)
{
}





void cSlotAreaEnchanting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));

	bool bAsync = false;
	if (GetSlot(a_SlotNum, a_Player) == nullptr)
	{
		LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
		return;
	}

	switch (a_ClickAction)
	{
		case caShiftLeftClick:
		case caShiftRightClick:
		{
			ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
			return;
		}
		case caDblClick:
		{
			DblClicked(a_Player, a_SlotNum);
			return;
		}
		case caMiddleClick:
		{
			MiddleClicked(a_Player, a_SlotNum);
			return;
		}
		case caDropKey:
		case caCtrlDropKey:
		{
			DropClicked(a_Player, a_SlotNum, false);
			return;
		}
		case caNumber1:
		case caNumber2:
		case caNumber3:
		case caNumber4:
		case caNumber5:
		case caNumber6:
		case caNumber7:
		case caNumber8:
		case caNumber9:
		{
			NumberClicked(a_Player, a_SlotNum, a_ClickAction);
			return;
		}
		default:
		{
			break;
		}
	}

	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	if (!Slot.IsSameType(a_ClickedItem))
	{
		LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
		LOGWARNING("My item:    %s", ItemToFullString(Slot).c_str());
		LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
		bAsync = true;
	}
	cItem & DraggingItem = a_Player.GetDraggingItem();

	if (DraggingItem.IsEmpty())
	{
		// DraggingItem is empty -> Switch draggingitem and slot
		if (!Slot.IsEmpty())
		{
			std::swap(DraggingItem, Slot);
		}
	}
	else if (Slot.IsEmpty())
	{
		// DraggingItem isn't empty and slot is empty -> Set one dragging item in the slot
		Slot = DraggingItem.CopyOne();
		DraggingItem.m_ItemCount -= 1;

		if (DraggingItem.m_ItemCount <= 0)
		{
			DraggingItem.Empty();
		}
	}
	else if ((DraggingItem.m_ItemCount == 1) && !DraggingItem.IsEqual(Slot))
	{
		// DraggingItem and slot aren't empty -> Switch items
		std::swap(DraggingItem, Slot);
	}

	SetSlot(a_SlotNum, a_Player, Slot);
	if (bAsync)
	{
		m_ParentWindow.BroadcastWholeWindow();
	}
}





void cSlotAreaEnchanting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_Apply, bool a_KeepEmptySlots, bool a_BackFill)
{
	const cItem * Slot = GetSlot(0, a_Player);
	if (!Slot->IsEmpty())
	{
		return;
	}

	if (a_Apply)
	{
		SetSlot(0, a_Player, a_ItemStack.CopyOne());
	}
	a_ItemStack.m_ItemCount -= 1;
	if (a_ItemStack.m_ItemCount <= 0)
	{
		a_ItemStack.Empty();
	}
}





void cSlotAreaEnchanting::OnPlayerAdded(cPlayer & a_Player)
{
	super::OnPlayerAdded(a_Player);
	UpdateResult(a_Player);
}





void cSlotAreaEnchanting::OnPlayerRemoved(cPlayer & a_Player)
{
	// Toss the item in the enchanting slot
	TossItems(a_Player, 0, 1);

	super::OnPlayerRemoved(a_Player);
}





void cSlotAreaEnchanting::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	super::SetSlot(a_SlotNum, a_Player, a_Item);
	UpdateResult(a_Player);
}





void cSlotAreaEnchanting::UpdateResult(cPlayer & a_Player)
{
	cItem Item = *GetSlot(0, a_Player);

	if (cItem::IsEnchantable(Item.m_ItemType) && Item.m_Enchantments.IsEmpty())
	{
		int Bookshelves = std::min(GetBookshelvesCount(*a_Player.GetWorld()), 15);

		auto & Random = GetRandomProvider();
		int Base = (Random.RandInt(1, 8) + (Bookshelves / 2) + Random.RandInt(0, Bookshelves));
		int TopSlot = std::max(Base / 3, 1);
		int MiddleSlot = (Base * 2) / 3 + 1;
		int BottomSlot = std::max(Base, Bookshelves * 2);

		m_ParentWindow.SetProperty(0, static_cast<short>(TopSlot), a_Player);
		m_ParentWindow.SetProperty(1, static_cast<short>(MiddleSlot), a_Player);
		m_ParentWindow.SetProperty(2, static_cast<short>(BottomSlot), a_Player);
	}
	else
	{
		m_ParentWindow.SetProperty(0, 0, a_Player);
		m_ParentWindow.SetProperty(1, 0, a_Player);
		m_ParentWindow.SetProperty(2, 0, a_Player);
	}
}





int cSlotAreaEnchanting::GetBookshelvesCount(cWorld & a_World)
{
	int Bookshelves = 0;
	cBlockArea Area;
	Area.Read(a_World, m_BlockX - 2, m_BlockX + 2, m_BlockY, m_BlockY + 1, m_BlockZ - 2, m_BlockZ + 2);

	static const struct
	{
		int m_BookX, m_BookY, m_BookZ;  // Coords to check for bookcases
		int m_AirX, m_AirY, m_AirZ;  // Coords to check for air; if not air, the bookcase won't be counted
	} CheckCoords[] =
	{
		{ 0, 0, 0, 1, 0, 1 },  // Bookcase at {0, 0, 0}, air at {1, 0, 1}
		{ 0, 0, 1, 1, 0, 1 },  // Bookcase at {0, 0, 1}, air at {1, 0, 1}
		{ 0, 0, 2, 1, 0, 2 },  // Bookcase at {0, 0, 2}, air at {1, 0, 2}
		{ 0, 0, 3, 1, 0, 3 },  // Bookcase at {0, 0, 3}, air at {1, 0, 3}
		{ 0, 0, 4, 1, 0, 3 },  // Bookcase at {0, 0, 4}, air at {1, 0, 3}
		{ 1, 0, 4, 1, 0, 3 },  // Bookcase at {1, 0, 4}, air at {1, 0, 3}
		{ 2, 0, 4, 2, 0, 3 },  // Bookcase at {2, 0, 4}, air at {2, 0, 3}
		{ 3, 0, 4, 3, 0, 3 },  // Bookcase at {3, 0, 4}, air at {3, 0, 3}
		{ 4, 0, 4, 3, 0, 3 },  // Bookcase at {4, 0, 4}, air at {3, 0, 3}
		{ 4, 0, 3, 3, 0, 3 },  // Bookcase at {4, 0, 3}, air at {3, 0, 3}
		{ 4, 0, 2, 3, 0, 2 },  // Bookcase at {4, 0, 2}, air at {3, 0, 2}
		{ 4, 0, 1, 3, 0, 1 },  // Bookcase at {4, 0, 1}, air at {3, 0, 1}
		{ 4, 0, 0, 3, 0, 1 },  // Bookcase at {4, 0, 0}, air at {3, 0, 1}
		{ 3, 0, 0, 3, 0, 1 },  // Bookcase at {3, 0, 0}, air at {3, 0, 1}
		{ 2, 0, 0, 2, 0, 1 },  // Bookcase at {2, 0, 0}, air at {2, 0, 1}
		{ 1, 0, 0, 1, 0, 1 },  // Bookcase at {1, 0, 0}, air at {1, 0, 1}

		{ 0, 1, 0, 1, 1, 1 },  // Bookcase at {0, 1, 0}, air at {1, 1, 1}
		{ 0, 1, 1, 1, 1, 1 },  // Bookcase at {0, 1, 1}, air at {1, 1, 1}
		{ 0, 1, 2, 1, 1, 2 },  // Bookcase at {0, 1, 2}, air at {1, 1, 2}
		{ 0, 1, 3, 1, 1, 3 },  // Bookcase at {0, 1, 3}, air at {1, 1, 3}
		{ 0, 1, 4, 1, 1, 3 },  // Bookcase at {0, 1, 4}, air at {1, 1, 3}
		{ 1, 1, 4, 1, 1, 3 },  // Bookcase at {1, 1, 4}, air at {1, 1, 3}
		{ 2, 1, 4, 2, 1, 3 },  // Bookcase at {2, 1, 4}, air at {2, 1, 3}
		{ 3, 1, 4, 3, 1, 3 },  // Bookcase at {3, 1, 4}, air at {3, 1, 3}
		{ 4, 1, 4, 3, 1, 3 },  // Bookcase at {4, 1, 4}, air at {3, 1, 3}
		{ 4, 1, 3, 3, 1, 3 },  // Bookcase at {4, 1, 3}, air at {3, 1, 3}
		{ 4, 1, 2, 3, 1, 2 },  // Bookcase at {4, 1, 2}, air at {3, 1, 2}
		{ 4, 1, 1, 3, 1, 1 },  // Bookcase at {4, 1, 1}, air at {3, 1, 1}
		{ 4, 1, 0, 3, 1, 1 },  // Bookcase at {4, 1, 0}, air at {3, 1, 1}
		{ 3, 1, 0, 3, 1, 1 },  // Bookcase at {3, 1, 0}, air at {3, 1, 1}
		{ 2, 1, 0, 2, 1, 1 },  // Bookcase at {2, 1, 0}, air at {2, 1, 1}
		{ 1, 1, 0, 1, 1, 1 },  // Bookcase at {1, 1, 0}, air at {1, 1, 1}
	};

	for (size_t i = 0; i < ARRAYCOUNT(CheckCoords); i++)
	{
		if (
			(Area.GetRelBlockType(CheckCoords[i].m_AirX, CheckCoords[i].m_AirY, CheckCoords[i].m_AirZ) == E_BLOCK_AIR) &&  // There's air in the checkspot
			(Area.GetRelBlockType(CheckCoords[i].m_BookX, CheckCoords[i].m_BookY, CheckCoords[i].m_BookZ) == E_BLOCK_BOOKCASE)  // There's bookcase in the wanted place
		)
		{
			Bookshelves++;
		}
	}  // for i - CheckCoords

	return Bookshelves;
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaEnderChest:

cSlotAreaEnderChest::cSlotAreaEnderChest(cEnderChestEntity * a_EnderChest, cWindow & a_ParentWindow) :
	cSlotArea(27, a_ParentWindow),
	m_EnderChest(a_EnderChest)
{
}





const cItem * cSlotAreaEnderChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	return &(a_Player.GetEnderChestContents().GetSlot(a_SlotNum));
}





void cSlotAreaEnderChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	a_Player.GetEnderChestContents().SetSlot(a_SlotNum, a_Item);
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaFurnace:

cSlotAreaFurnace::cSlotAreaFurnace(cFurnaceEntity * a_Furnace, cWindow & a_ParentWindow) :
	cSlotArea(3, a_ParentWindow),
	m_Furnace(a_Furnace)
{
	m_Furnace->GetContents().AddListener(*this);
}





cSlotAreaFurnace::~cSlotAreaFurnace()
{
	m_Furnace->GetContents().RemoveListener(*this);
}





void cSlotAreaFurnace::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	if (m_Furnace == nullptr)
	{
		LOGERROR("cSlotAreaFurnace::Clicked(): m_Furnace == nullptr");
		ASSERT(!"cSlotAreaFurnace::Clicked(): m_Furnace == nullptr");
		return;
	}

	if (a_SlotNum == 2)
	{
		bool bAsync = false;
		if (GetSlot(a_SlotNum, a_Player) == nullptr)
		{
			LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
			return;
		}

		cItem Slot(*GetSlot(a_SlotNum, a_Player));
		if (!Slot.IsSameType(a_ClickedItem))
		{
			LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
			LOGWARNING("My item:    %s", ItemToFullString(Slot).c_str());
			LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
			bAsync = true;
		}

		switch (a_ClickAction)
		{
			case caShiftLeftClick:
			case caShiftRightClick:
			{
				HandleSmeltItem(Slot, a_Player);
				ShiftClicked(a_Player, a_SlotNum, Slot);
				return;
			}
			case caMiddleClick:
			{
				MiddleClicked(a_Player, a_SlotNum);
				return;
			}
			case caDropKey:
			case caCtrlDropKey:
			{
				DropClicked(a_Player, a_SlotNum, (a_SlotNum == caCtrlDropKey));
				Slot.m_ItemCount = Slot.m_ItemCount - GetSlot(a_SlotNum, a_Player)->m_ItemCount;
				HandleSmeltItem(Slot, a_Player);
				return;
			}
			default:
			{
				break;
			}
		}

		cItem & DraggingItem = a_Player.GetDraggingItem();
		if (!DraggingItem.IsEmpty())
		{
			if (a_ClickAction == caDblClick)
			{
				return;
			}
			if (!DraggingItem.IsEqual(Slot))
			{
				return;
			}
			if ((DraggingItem.m_ItemCount + Slot.m_ItemCount) > Slot.GetMaxStackSize())
			{
				return;
			}

			DraggingItem.m_ItemCount += Slot.m_ItemCount;
			HandleSmeltItem(Slot, a_Player);
			Slot.Empty();
		}
		else
		{
			switch (a_ClickAction)
			{
				case caDblClick:
				{
					DblClicked(a_Player, a_SlotNum);
					return;
				}
				case caLeftClick:
				{
					DraggingItem = Slot;
					HandleSmeltItem(Slot, a_Player);
					Slot.Empty();
					break;
				}
				case caRightClick:
				{
					DraggingItem = Slot.CopyOne();
					DraggingItem.m_ItemCount = static_cast<char>(static_cast<float>(Slot.m_ItemCount) / 2.f + 0.5f);
					Slot.m_ItemCount -= DraggingItem.m_ItemCount;

					if (Slot.m_ItemCount <= 0)
					{
						Slot.Empty();
					}
					HandleSmeltItem(DraggingItem, a_Player);
					break;
				}
				default:
				{
					ASSERT(!"Unhandled click type!");
				}
			}
		}

		SetSlot(a_SlotNum, a_Player, Slot);
		if (bAsync)
		{
			m_ParentWindow.BroadcastWholeWindow();
		}
		return;
	}

	super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
}





void cSlotAreaFurnace::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
	int SlotNum;
	cFurnaceRecipe * FurnaceRecipes = cRoot::Get()->GetFurnaceRecipe();

	if (FurnaceRecipes->GetRecipeFrom(a_ItemStack) != nullptr)
	{
		SlotNum = 0;
	}
	else if (FurnaceRecipes->IsFuel(a_ItemStack))
	{
		SlotNum = 1;
	}
	else
	{
		return;
	}

	const cItem * Slot = GetSlot(SlotNum, a_Player);
	if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots))
	{
		// Different items
		return;
	}

	char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
	if (NumFit <= 0)
	{
		// Full stack already
		return;
	}
	NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);

	if (a_ShouldApply)
	{
		cItem NewSlot(a_ItemStack);
		NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
		SetSlot(SlotNum, a_Player, NewSlot);
	}
	a_ItemStack.m_ItemCount -= NumFit;
	if (a_ItemStack.IsEmpty())
	{
		return;
	}
}





const cItem * cSlotAreaFurnace::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	UNUSED(a_Player);
	// a_SlotNum ranges from 0 to 2, query the items from the underlying furnace:
	return &(m_Furnace->GetSlot(a_SlotNum));
}





void cSlotAreaFurnace::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	UNUSED(a_Player);
	m_Furnace->SetSlot(a_SlotNum, a_Item);
}





void cSlotAreaFurnace::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
	UNUSED(a_SlotNum);
	// Something has changed in the window, broadcast the entire window to all clients
	ASSERT(a_ItemGrid == &(m_Furnace->GetContents()));

	m_ParentWindow.BroadcastWholeWindow();
}





void cSlotAreaFurnace::HandleSmeltItem(const cItem & a_Result, cPlayer & a_Player)
{
	/** TODO 2014-05-12 xdot: Figure out when to call this method. */
	switch (a_Result.m_ItemType)
	{
		case E_ITEM_IRON:        a_Player.AwardAchievement(achAcquireIron); break;
		case E_ITEM_COOKED_FISH: a_Player.AwardAchievement(achCookFish);    break;
		default: break;
	}
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaBrewingstand:
cSlotAreaBrewingstand::cSlotAreaBrewingstand(cBrewingstandEntity * a_Brewingstand, cWindow & a_ParentWindow) :
	cSlotArea(5, a_ParentWindow),
	m_Brewingstand(a_Brewingstand)
{
	m_Brewingstand->GetContents().AddListener(*this);
}





cSlotAreaBrewingstand::~cSlotAreaBrewingstand()
{
	m_Brewingstand->GetContents().RemoveListener(*this);
}





void cSlotAreaBrewingstand::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	if (m_Brewingstand == nullptr)
	{
		LOGERROR("cSlotAreaBrewingstand::Clicked(): m_Brewingstand == nullptr");
		ASSERT(!"cSlotAreaBrewingstand::Clicked(): m_Brewingstand == nullptr");
		return;
	}

	if (GetSlot(a_SlotNum, a_Player) == nullptr)
	{
		LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
		return;
	}

	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	cItem & DraggingItem = a_Player.GetDraggingItem();
	cBrewingRecipes * BR = cRoot::Get()->GetBrewingRecipes();

	if ((a_SlotNum >= 0) && (a_SlotNum <= 2))
	{
		// Bottle slots
		switch (a_ClickAction)
		{
			case caLeftClick:
			case caRightClick:
			{
				if (BR->IsBottle(Slot))
				{
					HandleBrewedItem(a_Player, Slot);
				}
				if (!DraggingItem.IsEmpty() && !BR->IsBottle(DraggingItem))
				{
					// Deny placing a invalid item into the bottle slot
					return;
				}
				break;
			}
			case caShiftLeftClick:
			case caShiftRightClick:
			{
				if (BR->IsBottle(Slot))
				{
					HandleBrewedItem(a_Player, Slot);
				}
				super::ShiftClicked(a_Player, a_SlotNum, Slot);
				break;
			}
			default:
			{
				if (!DraggingItem.IsEmpty() && !BR->IsBottle(DraggingItem))
				{
					// Deny placing a invalid item into the bottle slot
					return;
				}
				break;
			}
		}
	}

	if ((a_SlotNum == 3) && !DraggingItem.IsEmpty())
	{
		// Ingredient slot
		switch (a_ClickAction)
		{
			case caShiftLeftClick:
			case caShiftRightClick:
			{
				super::ShiftClicked(a_Player, a_SlotNum, Slot);
				break;
			}
			default:
			{
				if (!BR->IsIngredient(DraggingItem))
				{
					// Deny placing a invalid item into the ingredient slot
					return;
				}
				break;
			}
		}
	}

	if ((a_SlotNum == 4) && !DraggingItem.IsEmpty())
	{
		// Fuel slot
		switch (a_ClickAction)
		{
			case caShiftLeftClick:
			case caShiftRightClick:
			{
				super::ShiftClicked(a_Player, a_SlotNum, Slot);
				break;
			}
			default:
			{
				if (!BR->IsFuel(DraggingItem))
				{
					// Deny placing a invalid item into the fuel slot
					return;
				}
				break;
			}
		}
	}

	super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
}





void cSlotAreaBrewingstand::HandleBrewedItem(cPlayer & a_Player, const cItem & a_ClickedItem)
{
	// Award an achievement if the item is not a water bottle (is a real brewed potion)
	if (a_ClickedItem.m_ItemDamage > 0)
	{
		a_Player.AwardAchievement(achBrewPotion);
	}
}





void cSlotAreaBrewingstand::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
	int SlotNum = -1;
	cBrewingRecipes * BR = cRoot::Get()->GetBrewingRecipes();
	if (BR->IsBottle(a_ItemStack))
	{
		for (int i = 0;i < 3;i++)
		{
			if (GetSlot(i, a_Player)->IsEmpty())
			{
				SlotNum = i;
				break;
			}
		}

		if (SlotNum == -1)
		{
			// All slots are full
			return;
		}
	}
	else if (BR->IsIngredient(a_ItemStack))
	{
		SlotNum = 3;
	}
	else
	{
		return;
	}

	const cItem * Slot = GetSlot(SlotNum, a_Player);
	if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots))
	{
		// Different items
		return;
	}

	char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
	if (NumFit <= 0)
	{
		// Full stack already
		return;
	}
	NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);

	if (a_ShouldApply)
	{
		cItem NewSlot(a_ItemStack);
		NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
		SetSlot(SlotNum, a_Player, NewSlot);
	}
	a_ItemStack.m_ItemCount -= NumFit;
	if (a_ItemStack.IsEmpty())
	{
		return;
	}
}





const cItem * cSlotAreaBrewingstand::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	UNUSED(a_Player);
	// a_SlotNum ranges from 0 to 3, query the items from the underlying brewing stand:
	return &(m_Brewingstand->GetSlot(a_SlotNum));
}





void cSlotAreaBrewingstand::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	UNUSED(a_Player);
	m_Brewingstand->SetSlot(a_SlotNum, a_Item);
}





void cSlotAreaBrewingstand::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
	UNUSED(a_SlotNum);
	// Something has changed in the window, broadcast the entire window to all clients
	ASSERT(a_ItemGrid == &(m_Brewingstand->GetContents()));

	m_ParentWindow.BroadcastWholeWindow();
}


////////////////////////////////////////////////////////////////////////////////
// cSlotAreaMinecartWithChest:

cSlotAreaMinecartWithChest::cSlotAreaMinecartWithChest(cMinecartWithChest * a_Chest, cWindow & a_ParentWindow) :
	cSlotArea(27, a_ParentWindow),
	m_Chest(a_Chest)
{
}





const cItem * cSlotAreaMinecartWithChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	// a_SlotNum ranges from 0 to 26, use that to index the minecart chest entity's inventory directly:
	UNUSED(a_Player);
	return &(m_Chest->GetSlot(a_SlotNum));
}





void cSlotAreaMinecartWithChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	UNUSED(a_Player);
	m_Chest->SetSlot(a_SlotNum, a_Item);
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaVillagerTrade:

cSlotAreaVillagerTrade::cSlotAreaVillagerTrade(cVillager & Villager, cWindow & ParentWindow) :
	cSlotAreaTemporary(3, ParentWindow),
	m_Villager(Villager)
{
}





void cSlotAreaVillagerTrade::UpdateTrade(const cPlayer & TradingPlayer)
{
	const auto & Contents = GetPlayerSlots(TradingPlayer);
	const auto & Trade = m_Villager.GetTradeOffer(
		static_cast<cVillagerTradeWindow *>(&m_ParentWindow)->GetPlayerTradeOfferIndex(TradingPlayer),
		Contents[SlotIndices::PrimaryDesire],
		Contents[SlotIndices::SecondaryDesire]
	);
	const auto TransactionMultiplier = cVillager::GetTransactionMultiplier(Trade, Contents[SlotIndices::PrimaryDesire], Contents[SlotIndices::SecondaryDesire]);

	if (TransactionMultiplier != 0)
	{
		cItem Recompense(Trade.Recompense);
		Recompense.m_ItemCount *= TransactionMultiplier;
		Contents[SlotIndices::Recompense] = Recompense;
	}
	else
	{
		Contents[SlotIndices::Recompense].Empty();
	}
}





void cSlotAreaVillagerTrade::SetSlot(int ActionIndex, cPlayer & TradingPlayer, const cItem & Item)
{
	const auto & Contents = GetPlayerSlots(TradingPlayer);
	const auto & RecompenseSlot = Contents[SlotIndices::Recompense];

	if ((ActionIndex == SlotIndices::Recompense) && !RecompenseSlot.IsEmpty())
	{
		// Player clicked recompense slot. Slot was populated, meaning the trade can go ahead.
		// Note that parameter Item holds the new remaining count of compensation items (if any)

		auto & Trade = m_Villager.GetTradeOffer(
			static_cast<cVillagerTradeWindow *>(&m_ParentWindow)->GetPlayerTradeOfferIndex(TradingPlayer),
			Contents[SlotIndices::PrimaryDesire],
			Contents[SlotIndices::SecondaryDesire]
		);
		const auto ActualTransactionMultiplier = static_cast<unsigned>((RecompenseSlot.m_ItemCount - Item.m_ItemCount) / Trade.Recompense.m_ItemCount);

		if (ActualTransactionMultiplier == 0)
		{
			// Not strictly needed in terms of preserving item count integrity but nothing will change so exit early
			// Prevents associated villager from responding to a "trade" with no items exchanged
			return;
		}

		if (Contents[SlotIndices::PrimaryDesire].IsEmpty())
		{
			// (Given that a trade can be made), player must have placed primary desire into secondary slot

			ASSERT(Trade.SecondaryDesire.IsEmpty());
			Contents[SlotIndices::SecondaryDesire].m_ItemCount -= Trade.PrimaryDesire.m_ItemCount * static_cast<char>(ActualTransactionMultiplier);
		}
		else
		{
			// Primary\secondary desire\offer slots match up respectively

			Contents[SlotIndices::PrimaryDesire].m_ItemCount -= Trade.PrimaryDesire.m_ItemCount * static_cast<char>(ActualTransactionMultiplier);
			Contents[SlotIndices::SecondaryDesire].m_ItemCount -= Trade.SecondaryDesire.m_ItemCount * static_cast<char>(ActualTransactionMultiplier);
		}

		m_Villager.HandleTransaction(Trade, ActualTransactionMultiplier);
	}

	super::SetSlot(ActionIndex, TradingPlayer, Item);
}





void cSlotAreaVillagerTrade::OnPlayerRemoved(cPlayer & Player)
{
	TossItems(Player, SlotIndices::PrimaryDesire, SlotIndices::SecondaryDesire);
	super::OnPlayerRemoved(Player);
}





void cSlotAreaVillagerTrade::Clicked(cPlayer & Player, int SlotNumber, eClickAction ClickAction, const cItem & ClickedItem)
{
	auto & DraggingItem = Player.GetDraggingItem();
	if (
		(SlotNumber == SlotIndices::Recompense) &&
		((ClickAction == eClickAction::caLeftClick) || (ClickAction == eClickAction::caRightClick))
	)
	{
		cItem RecompenseSlot = *GetSlot(SlotIndices::Recompense, Player);
		if (RecompenseSlot.IsEmpty())
		{
			// No trade possible right now
			return;
		}

		// Single clicking on recompense slot should transfer one transaction's worth of items to the hand
		// As long as there is no dragging item or the dragging item is of the same type as the compensation

		const auto MoveCount = m_Villager.GetTradeOffer(
			static_cast<cVillagerTradeWindow *>(&m_ParentWindow)->GetPlayerTradeOfferIndex(Player),
			*GetSlot(SlotIndices::PrimaryDesire, Player),
			*GetSlot(SlotIndices::SecondaryDesire, Player)
		).Recompense.m_ItemCount;
		if (DraggingItem.IsEmpty())
		{
			DraggingItem = RecompenseSlot;
			DraggingItem.m_ItemCount = MoveCount;
			RecompenseSlot.m_ItemCount -= MoveCount;
			SetSlot(SlotIndices::Recompense, Player, RecompenseSlot);  // Perform transaction
		}
		else if (DraggingItem.IsEqual(RecompenseSlot))
		{
			if (DraggingItem.GetMaxStackSize() < (DraggingItem.m_ItemCount + MoveCount))
			{
				return;
			}

			DraggingItem.m_ItemCount += MoveCount;
			RecompenseSlot.m_ItemCount -= MoveCount;
			SetSlot(SlotIndices::Recompense, Player, RecompenseSlot);  // Perform transaction
		}

		return;
	}

	super::Clicked(Player, SlotNumber, ClickAction, ClickedItem);
	UpdateTrade(Player);

	if (GetSlot(SlotIndices::Recompense, Player)->IsEmpty())
	{
		m_Villager.HandleTradeUnavailable();
	}
	else
	{
		m_Villager.HandleTradeAvailable();
	}
}





void cSlotAreaVillagerTrade::NumberClicked(cPlayer & Player, int SlotNumber, eClickAction ClickAction)
{
	if (SlotNumber != SlotIndices::Recompense)
	{
		super::NumberClicked(Player, SlotNumber, ClickAction);
		return;
	}

	int HotbarSlotIndex = static_cast<int>(ClickAction - caNumber1);
	cItem HotbarSlot(Player.GetInventory().GetHotbarSlot(HotbarSlotIndex));
	cItem RecompenseSlot(*GetSlot(SlotNumber, Player));

	if (!HotbarSlot.IsEmpty())
	{
		// Prevent items being inserted into trade compensation slot
		return;
	}

	const auto MoveCount = m_Villager.GetTradeOffer(
		static_cast<cVillagerTradeWindow *>(&m_ParentWindow)->GetPlayerTradeOfferIndex(Player),
		*GetSlot(SlotIndices::PrimaryDesire, Player),
		*GetSlot(SlotIndices::SecondaryDesire, Player)
	).Recompense.m_ItemCount;

	HotbarSlot = RecompenseSlot;
	HotbarSlot.m_ItemCount = MoveCount;
	RecompenseSlot.m_ItemCount -= MoveCount;

	Player.GetInventory().SetHotbarSlot(HotbarSlotIndex, HotbarSlot);
	SetSlot(SlotNumber, Player, RecompenseSlot);
}





void cSlotAreaVillagerTrade::DblClicked(cPlayer & Player, int SlotNumber)
{
	if (SlotNumber == SlotIndices::Recompense)
	{
		return;
	}

	super::DblClicked(Player, SlotNumber);
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaInventoryBase:

cSlotAreaInventoryBase::cSlotAreaInventoryBase(int a_NumSlots, int a_SlotOffset, cWindow & a_ParentWindow) :
	cSlotArea(a_NumSlots, a_ParentWindow),
	m_SlotOffset(a_SlotOffset)
{
}





void cSlotAreaInventoryBase::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	if (a_Player.IsGameModeCreative() && (m_ParentWindow.GetWindowType() == cWindow::wtInventory))
	{
		if ((a_ClickAction == caDropKey) || (a_ClickAction == caCtrlDropKey))
		{
			DropClicked(a_Player, a_SlotNum, (a_ClickAction == caCtrlDropKey));
			return;
		}

		// Creative inventory must treat a_ClickedItem as a DraggedItem instead, replacing the inventory slot with it
		SetSlot(a_SlotNum, a_Player, a_ClickedItem);
		return;
	}

	// Survival inventory and all other windows' inventory has the same handling as normal slot areas
	super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
	return;
}





const cItem * cSlotAreaInventoryBase::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	// a_SlotNum ranges from 0 to 35, map that to the player's inventory slots according to the internal offset
	return &a_Player.GetInventory().GetSlot(a_SlotNum + m_SlotOffset);
}





void cSlotAreaInventoryBase::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	a_Player.GetInventory().SetSlot(a_SlotNum + m_SlotOffset, a_Item);
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaArmor:

void cSlotAreaArmor::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
	if (ItemCategory::IsHelmet(a_ItemStack.m_ItemType) && GetSlot(0, a_Player)->IsEmpty())
	{
		if (a_ShouldApply)
		{
			SetSlot(0, a_Player, a_ItemStack.CopyOne());
		}
		a_ItemStack.m_ItemCount -= 1;
	}
	else if (ItemCategory::IsChestPlate(a_ItemStack.m_ItemType) && GetSlot(1, a_Player)->IsEmpty())
	{
		if (a_ShouldApply)
		{
			SetSlot(1, a_Player, a_ItemStack.CopyOne());
		}
		a_ItemStack.m_ItemCount -= 1;
	}
	else if (ItemCategory::IsLeggings(a_ItemStack.m_ItemType) && GetSlot(2, a_Player)->IsEmpty())
	{
		if (a_ShouldApply)
		{
			SetSlot(2, a_Player, a_ItemStack.CopyOne());
		}
		a_ItemStack.m_ItemCount -= 1;
	}
	else if (ItemCategory::IsBoots(a_ItemStack.m_ItemType) && GetSlot(3, a_Player)->IsEmpty())
	{
		if (a_ShouldApply)
		{
			SetSlot(3, a_Player, a_ItemStack.CopyOne());
		}
		a_ItemStack.m_ItemCount -= 1;
	}
}





void cSlotAreaArmor::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
	ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));

	// When the player is in creative mode, the client sends the new item as a_ClickedItem, not the current item in the slot.
	if (a_Player.IsGameModeCreative() && (m_ParentWindow.GetWindowType() == cWindow::wtInventory))
	{
		if ((a_ClickAction == caDropKey) || (a_ClickAction == caCtrlDropKey))
		{
			DropClicked(a_Player, a_SlotNum, (a_ClickAction == caCtrlDropKey));
			return;
		}

		SetSlot(a_SlotNum, a_Player, a_ClickedItem);
		return;
	}

	bool bAsync = false;
	if (GetSlot(a_SlotNum, a_Player) == nullptr)
	{
		LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
		return;
	}

	switch (a_ClickAction)
	{
		case caDblClick:
		{
			// Armors haven't a dbl click
			return;
		}
		case caShiftLeftClick:
		case caShiftRightClick:
		{
			ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
			return;
		}
		case caMiddleClick:
		{
			MiddleClicked(a_Player, a_SlotNum);
			return;
		}
		default:
		{
			break;
		}
	}

	cItem Slot(*GetSlot(a_SlotNum, a_Player));
	if (!Slot.IsSameType(a_ClickedItem))
	{
		LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
		LOGWARNING("My item:    %s", ItemToFullString(Slot).c_str());
		LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
		bAsync = true;
	}
	cItem & DraggingItem = a_Player.GetDraggingItem();
	if ((a_ClickAction != caRightClick) && (a_ClickAction != caLeftClick))
	{
		LOGWARNING("SlotArea: Unhandled click action: %d (%s)", a_ClickAction, ClickActionToString(a_ClickAction));
		m_ParentWindow.BroadcastWholeWindow();
		return;
	}

	if (DraggingItem.IsEmpty() || CanPlaceArmorInSlot(a_SlotNum, DraggingItem))
	{
		// Swap contents
		cItem tmp(DraggingItem);
		DraggingItem = Slot;
		Slot = tmp;
	}

	SetSlot(a_SlotNum, a_Player, Slot);
	if (bAsync)
	{
		m_ParentWindow.BroadcastWholeWindow();
	}
}





bool cSlotAreaArmor::CanPlaceArmorInSlot(int a_SlotNum, const cItem & a_Item)
{
	switch (a_SlotNum)
	{
		case 0:  return (ItemCategory::IsHelmet(a_Item.m_ItemType) || (a_Item.m_ItemType == E_BLOCK_PUMPKIN));
		case 1:  return ItemCategory::IsChestPlate(a_Item.m_ItemType);
		case 2:  return ItemCategory::IsLeggings(a_Item.m_ItemType);
		case 3:  return ItemCategory::IsBoots(a_Item.m_ItemType);
	}
	return false;
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaItemGrid:

cSlotAreaItemGrid::cSlotAreaItemGrid(cItemGrid & a_ItemGrid, cWindow & a_ParentWindow) :
	super(a_ItemGrid.GetNumSlots(), a_ParentWindow),
	m_ItemGrid(a_ItemGrid)
{
	m_ItemGrid.AddListener(*this);
}





cSlotAreaItemGrid::~cSlotAreaItemGrid()
{
	m_ItemGrid.RemoveListener(*this);
}





const cItem * cSlotAreaItemGrid::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	return &m_ItemGrid.GetSlot(a_SlotNum);
}





void cSlotAreaItemGrid::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	m_ItemGrid.SetSlot(a_SlotNum, a_Item);
}





void cSlotAreaItemGrid::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
	ASSERT(a_ItemGrid == &m_ItemGrid);
	m_ParentWindow.BroadcastSlot(this, a_SlotNum);
}





////////////////////////////////////////////////////////////////////////////////
// cSlotAreaTemporary:

cSlotAreaTemporary::cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow) :
	cSlotArea(a_NumSlots, a_ParentWindow)
{
}





const cItem * cSlotAreaTemporary::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
	cItemMap::const_iterator itr = m_Items.find(a_Player.GetUniqueID());
	if (itr == m_Items.end())
	{
		LOGERROR("cSlotAreaTemporary: player \"%s\" not found for slot %d!", a_Player.GetName().c_str(), a_SlotNum);
		ASSERT(!"cSlotAreaTemporary: player not found!");

		// Player not found, this should not happen, ever! Return nullptr, but things may break by this.
		return nullptr;
	}

	if (a_SlotNum >= static_cast<int>(itr->second.size()))
	{
		LOGERROR("cSlotAreaTemporary: asking for more slots than actually stored!");
		ASSERT(!"cSlotAreaTemporary: asking for more slots than actually stored!");
		return nullptr;
	}

	return &(itr->second[static_cast<size_t>(a_SlotNum)]);
}





void cSlotAreaTemporary::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
	cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID());
	if (itr == m_Items.end())
	{
		// Player not found
		LOGWARNING("cSlotAreaTemporary: player not found!");
		return;
	}

	if (a_SlotNum >= static_cast<int>(itr->second.size()))
	{
		LOGERROR("cSlotAreaTemporary: asking for more slots than actually stored!");
		return;
	}

	itr->second[static_cast<size_t>(a_SlotNum)] = a_Item;

	m_ParentWindow.SendSlot(a_Player, this, a_SlotNum);
}





void cSlotAreaTemporary::OnPlayerAdded(cPlayer & a_Player)
{
	ASSERT(m_Items.find(a_Player.GetUniqueID()) == m_Items.end());  // The player shouldn't be in the itemmap, otherwise we probably have a leak
	m_Items[a_Player.GetUniqueID()].resize(static_cast<size_t>(m_NumSlots));  // Make the vector the specified size of empty items
}





void cSlotAreaTemporary::OnPlayerRemoved(cPlayer & a_Player)
{
	cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID());
	ASSERT(itr != m_Items.end());  // The player should be in the list, otherwise a call to OnPlayerAdded() was mismatched
	m_Items.erase(itr);
}





void cSlotAreaTemporary::TossItems(cPlayer & a_Player, int a_Begin, int a_End)
{
	cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID());
	if (itr == m_Items.end())
	{
		LOGWARNING("Player tossing items (%s) not found in the item map", a_Player.GetName().c_str());
		return;
	}

	cItems Drops;
	for (int i = a_Begin; i < a_End; i++)
	{
		cItem & Item = itr->second[static_cast<size_t>(i)];
		if (!Item.IsEmpty())
		{
			Drops.push_back(Item);
		}
		Item.Empty();
	}  // for i - itr->second[]

	double vX = 0, vY = 0, vZ = 0;
	EulerToVector(-a_Player.GetYaw(), a_Player.GetPitch(), vZ, vX, vY);
	vY = -vY * 2 + 1.f;
	a_Player.GetWorld()->SpawnItemPickups(Drops, a_Player.GetPosX(), a_Player.GetPosY() + 1.6f, a_Player.GetPosZ(), vX * 3, vY * 3, vZ * 3, true);  // 'true' because player created
}





cItem * cSlotAreaTemporary::GetPlayerSlots(const cPlayer & a_Player)
{
	cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID());
	if (itr == m_Items.end())
	{
		return nullptr;
	}
	return itr->second.data();
}