diff options
Diffstat (limited to 'src/BlockEntities/HopperEntity.cpp')
-rw-r--r-- | src/BlockEntities/HopperEntity.cpp | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/src/BlockEntities/HopperEntity.cpp b/src/BlockEntities/HopperEntity.cpp new file mode 100644 index 000000000..41849b1b3 --- /dev/null +++ b/src/BlockEntities/HopperEntity.cpp @@ -0,0 +1,566 @@ + +// HopperEntity.cpp + +// Implements the cHopperEntity representing a hopper block entity + +#include "Globals.h" +#include "HopperEntity.h" +#include "../Chunk.h" +#include "../Entities/Player.h" +#include "../PluginManager.h" +#include "ChestEntity.h" +#include "DropSpenserEntity.h" +#include "FurnaceEntity.h" + + + + + +cHopperEntity::cHopperEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World) : + super(E_BLOCK_HOPPER, a_BlockX, a_BlockY, a_BlockZ, ContentsWidth, ContentsHeight, a_World), + m_LastMoveItemsInTick(0), + m_LastMoveItemsOutTick(0) +{ +} + + + + + +/** Returns the block coords of the block receiving the output items, based on the meta +Returns false if unattached +*/ +bool cHopperEntity::GetOutputBlockPos(NIBBLETYPE a_BlockMeta, int & a_OutputX, int & a_OutputY, int & a_OutputZ) +{ + a_OutputX = m_PosX; + a_OutputY = m_PosY; + a_OutputZ = m_PosZ; + switch (a_BlockMeta) + { + case E_META_HOPPER_FACING_XM: a_OutputX--; return true; + case E_META_HOPPER_FACING_XP: a_OutputX++; return true; + case E_META_HOPPER_FACING_YM: a_OutputY--; return true; + case E_META_HOPPER_FACING_ZM: a_OutputZ--; return true; + case E_META_HOPPER_FACING_ZP: a_OutputZ++; return true; + default: + { + // Not attached + return false; + } + } +} + + + + + +bool cHopperEntity::Tick(float a_Dt, cChunk & a_Chunk) +{ + Int64 CurrentTick = a_Chunk.GetWorld()->GetWorldAge(); + + bool res = false; + res = MoveItemsIn (a_Chunk, CurrentTick) || res; + res = MovePickupsIn(a_Chunk, CurrentTick) || res; + res = MoveItemsOut (a_Chunk, CurrentTick) || res; + return res; +} + + + + + +void cHopperEntity::SaveToJson(Json::Value & a_Value) +{ + // TODO + LOGWARNING("%s: Not implemented yet", __FUNCTION__); +} + + + + + +void cHopperEntity::SendTo(cClientHandle & a_Client) +{ + // The hopper entity doesn't need anything sent to the client when it's created / gets in the viewdistance + // All the actual handling is in the cWindow UI code that gets called when the hopper is rclked + + UNUSED(a_Client); +} + + + + + +void cHopperEntity::UsedBy(cPlayer * a_Player) +{ + // If the window is not created, open it anew: + cWindow * Window = GetWindow(); + if (Window == NULL) + { + OpenNewWindow(); + Window = GetWindow(); + } + + // Open the window for the player: + if (Window != NULL) + { + if (a_Player->GetWindow() != Window) + { + a_Player->OpenWindow(Window); + } + } + + // This is rather a hack + // Instead of marking the chunk as dirty upon chest contents change, we mark it dirty now + // We cannot properly detect contents change, but such a change doesn't happen without a player opening the chest first. + // The few false positives aren't much to worry about + int ChunkX, ChunkZ; + cChunkDef::BlockToChunk(m_PosX, m_PosZ, ChunkX, ChunkZ); + m_World->MarkChunkDirty(ChunkX, ChunkZ); +} + + + + + +/// Opens a new window UI for this hopper +void cHopperEntity::OpenNewWindow(void) +{ + OpenWindow(new cHopperWindow(m_PosX, m_PosY, m_PosZ, this)); +} + + + + + +/// Moves items from the container above it into this hopper. Returns true if the contents have changed. +bool cHopperEntity::MoveItemsIn(cChunk & a_Chunk, Int64 a_CurrentTick) +{ + if (m_PosY >= cChunkDef::Height) + { + // This hopper is at the top of the world, no more blocks above + return false; + } + + if (a_CurrentTick - m_LastMoveItemsInTick < TICKS_PER_TRANSFER) + { + // Too early after the previous transfer + return false; + } + + // Try moving an item in: + bool res = false; + switch (a_Chunk.GetBlock(m_RelX, m_PosY + 1, m_RelZ)) + { + case E_BLOCK_CHEST: + { + // Chests have special handling because of double-chests + res = MoveItemsFromChest(a_Chunk); + break; + } + case E_BLOCK_LIT_FURNACE: + case E_BLOCK_FURNACE: + { + // Furnaces have special handling because only the output and leftover fuel buckets shall be moved + res = MoveItemsFromFurnace(a_Chunk); + break; + } + case E_BLOCK_DISPENSER: + case E_BLOCK_DROPPER: + case E_BLOCK_HOPPER: + { + res = MoveItemsFromGrid(*(cBlockEntityWithItems *)a_Chunk.GetBlockEntity(m_PosX, m_PosY + 1, m_PosZ)); + break; + } + } + + // If the item has been moved, reset the last tick: + if (res) + { + m_LastMoveItemsInTick = a_CurrentTick; + } + + return res; +} + + + + + +/// Moves pickups from above this hopper into it. Returns true if the contents have changed. +bool cHopperEntity::MovePickupsIn(cChunk & a_Chunk, Int64 a_CurrentTick) +{ + // TODO + return false; +} + + + + + +/// Moves items out from this hopper into the destination. Returns true if the contents have changed. +bool cHopperEntity::MoveItemsOut(cChunk & a_Chunk, Int64 a_CurrentTick) +{ + if (a_CurrentTick - m_LastMoveItemsOutTick < TICKS_PER_TRANSFER) + { + // Too early after the previous transfer + return false; + } + + int bx, by, bz; + NIBBLETYPE Meta = a_Chunk.GetMeta(m_RelX, m_PosY, m_RelZ); + if (!GetOutputBlockPos(Meta, bx, by, bz)) + { + // Not attached to another container + return false; + } + if (by < 0) + { + // Cannot output below the zero-th block level + return false; + } + + // Convert coords to relative: + int rx = bx - a_Chunk.GetPosX() * cChunkDef::Width; + int rz = bz - a_Chunk.GetPosZ() * cChunkDef::Width; + cChunk * DestChunk = a_Chunk.GetRelNeighborChunkAdjustCoords(rx, rz); + if (DestChunk == NULL) + { + // The destination chunk has been unloaded, don't tick + return false; + } + + // Call proper moving function, based on the blocktype present at the coords: + bool res = false; + switch (DestChunk->GetBlock(rx, by, rz)) + { + case E_BLOCK_CHEST: + { + // Chests have special handling because of double-chests + res = MoveItemsToChest(*DestChunk, bx, by, bz); + break; + } + case E_BLOCK_LIT_FURNACE: + case E_BLOCK_FURNACE: + { + // Furnaces have special handling because of the direction-to-slot relation + res = MoveItemsToFurnace(*DestChunk, bx, by, bz, Meta); + break; + } + case E_BLOCK_DISPENSER: + case E_BLOCK_DROPPER: + case E_BLOCK_HOPPER: + { + res = MoveItemsToGrid(*(cBlockEntityWithItems *)DestChunk->GetBlockEntity(bx, by, bz)); + break; + } + } + + // If the item has been moved, reset the last tick: + if (res) + { + m_LastMoveItemsOutTick = a_CurrentTick; + } + + return res; +} + + + + + +/// Moves items from a chest (dblchest) above the hopper into this hopper. Returns true if contents have changed. +bool cHopperEntity::MoveItemsFromChest(cChunk & a_Chunk) +{ + if (MoveItemsFromGrid(*(cChestEntity *)a_Chunk.GetBlockEntity(m_PosX, m_PosY + 1, m_PosZ))) + { + // Moved the item from the chest directly above the hopper + return true; + } + + // Check if the chest is a double-chest, if so, try to move from there: + static const struct + { + int x, z; + } + Coords [] = + { + {1, 0}, + {-1, 0}, + {0, 1}, + {0, -1}, + } ; + for (int i = 0; i < ARRAYCOUNT(Coords); i++) + { + int x = m_RelX + Coords[i].x; + int z = m_RelZ + Coords[i].z; + cChunk * Neighbor = a_Chunk.GetRelNeighborChunkAdjustCoords(x, z); + if ( + (Neighbor == NULL) || + (Neighbor->GetBlock(x, m_PosY + 1, z) != E_BLOCK_CHEST) + ) + { + continue; + } + if (MoveItemsFromGrid(*(cChestEntity *)Neighbor->GetBlockEntity(x, m_PosY, z))) + { + return true; + } + return false; + } + + // The chest was single and nothing could be moved + return false; +} + + + + + +/// Moves items from a furnace above the hopper into this hopper. Returns true if contents have changed. +bool cHopperEntity::MoveItemsFromFurnace(cChunk & a_Chunk) +{ + cFurnaceEntity * Furnace = (cFurnaceEntity *)a_Chunk.GetBlockEntity(m_PosX, m_PosY + 1, m_PosZ); + ASSERT(Furnace != NULL); + + // Try move from the output slot: + if (MoveItemsFromSlot(*Furnace, cFurnaceEntity::fsOutput, true)) + { + cItem NewOutput(Furnace->GetOutputSlot()); + Furnace->SetOutputSlot(NewOutput.AddCount(-1)); + return true; + } + + // No output moved, check if we can move an empty bucket out of the fuel slot: + if (Furnace->GetFuelSlot().m_ItemType == E_ITEM_BUCKET) + { + if (MoveItemsFromSlot(*Furnace, cFurnaceEntity::fsFuel, true)) + { + Furnace->SetFuelSlot(cItem()); + return true; + } + } + + // Nothing can be moved + return false; +} + + + + + +bool cHopperEntity::MoveItemsFromGrid(cBlockEntityWithItems & a_Entity) +{ + cItemGrid & Grid = a_Entity.GetContents(); + int NumSlots = Grid.GetNumSlots(); + + // First try adding items of types already in the hopper: + for (int i = 0; i < NumSlots; i++) + { + if (Grid.IsSlotEmpty(i)) + { + continue; + } + if (MoveItemsFromSlot(a_Entity, i, false)) + { + Grid.ChangeSlotCount(i, -1); + return true; + } + } + + // No already existing stack can be topped up, try again with allowing new stacks: + for (int i = 0; i < NumSlots; i++) + { + if (Grid.IsSlotEmpty(i)) + { + continue; + } + if (MoveItemsFromSlot(a_Entity, i, true)) + { + Grid.ChangeSlotCount(i, -1); + return true; + } + } + return false; +} + + + + + +/// Moves one piece of the specified a_Entity's slot itemstack into this hopper. Returns true if contents have changed. Doesn't change the itemstack. +bool cHopperEntity::MoveItemsFromSlot(cBlockEntityWithItems & a_Entity, int a_SlotNum, bool a_AllowNewStacks) +{ + cItem One(a_Entity.GetSlot(a_SlotNum).CopyOne()); + for (int i = 0; i < ContentsWidth * ContentsHeight; i++) + { + if (m_Contents.IsSlotEmpty(i)) + { + if (a_AllowNewStacks) + { + if (cPluginManager::Get()->CallHookHopperPullingItem(*m_World, *this, i, a_Entity, a_SlotNum)) + { + // Plugin disagrees with the move + continue; + } + } + m_Contents.SetSlot(i, One); + return true; + } + else if (m_Contents.GetSlot(i).IsStackableWith(One)) + { + if (cPluginManager::Get()->CallHookHopperPullingItem(*m_World, *this, i, a_Entity, a_SlotNum)) + { + // Plugin disagrees with the move + continue; + } + + m_Contents.ChangeSlotCount(i, 1); + return true; + } + } + return false; +} + + + + + +/// Moves items to the chest at the specified coords. Returns true if contents have changed +bool cHopperEntity::MoveItemsToChest(cChunk & a_Chunk, int a_BlockX, int a_BlockY, int a_BlockZ) +{ + // Try the chest directly connected to the hopper: + if (MoveItemsToGrid(*(cChestEntity *)a_Chunk.GetBlockEntity(a_BlockX, a_BlockY, a_BlockZ))) + { + return true; + } + + // Check if the chest is a double-chest, if so, try to move into the other half: + static const struct + { + int x, z; + } + Coords [] = + { + {1, 0}, + {-1, 0}, + {0, 1}, + {0, -1}, + } ; + for (int i = 0; i < ARRAYCOUNT(Coords); i++) + { + int x = m_RelX + Coords[i].x; + int z = m_RelZ + Coords[i].z; + cChunk * Neighbor = a_Chunk.GetRelNeighborChunkAdjustCoords(x, z); + if ( + (Neighbor == NULL) || + (Neighbor->GetBlock(x, m_PosY + 1, z) != E_BLOCK_CHEST) + ) + { + continue; + } + if (MoveItemsToGrid(*(cChestEntity *)Neighbor->GetBlockEntity(a_BlockX, a_BlockY, a_BlockZ))) + { + return true; + } + return false; + } + + // The chest was single and nothing could be moved + return false; +} + + + + + +/// Moves items to the furnace at the specified coords. Returns true if contents have changed +bool cHopperEntity::MoveItemsToFurnace(cChunk & a_Chunk, int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_HopperMeta) +{ + cFurnaceEntity * Furnace = (cFurnaceEntity *)a_Chunk.GetBlockEntity(a_BlockX, a_BlockY, a_BlockZ); + if (a_HopperMeta == E_META_HOPPER_FACING_YM) + { + // Feed the input slot of the furnace + return MoveItemsToSlot(*Furnace, cFurnaceEntity::fsInput); + } + else + { + // Feed the fuel slot of the furnace + return MoveItemsToSlot(*Furnace, cFurnaceEntity::fsFuel); + } + return false; +} + + + + + +bool cHopperEntity::MoveItemsToGrid(cBlockEntityWithItems & a_Entity) +{ + // Iterate through our slots, try to move from each one: + int NumSlots = a_Entity.GetContents().GetNumSlots(); + for (int i = 0; i < NumSlots; i++) + { + if (MoveItemsToSlot(a_Entity, i)) + { + return true; + } + } + return false; +} + + + + + +bool cHopperEntity::MoveItemsToSlot(cBlockEntityWithItems & a_Entity, int a_DstSlotNum) +{ + cItemGrid & Grid = a_Entity.GetContents(); + if (Grid.IsSlotEmpty(a_DstSlotNum)) + { + // The slot is empty, move the first non-empty slot from our contents: + for (int i = 0; i < ContentsWidth * ContentsHeight; i++) + { + if (!m_Contents.IsSlotEmpty(i)) + { + if (cPluginManager::Get()->CallHookHopperPushingItem(*m_World, *this, i, a_Entity, a_DstSlotNum)) + { + // A plugin disagrees with the move + continue; + } + Grid.SetSlot(a_DstSlotNum, m_Contents.GetSlot(i).CopyOne()); + m_Contents.ChangeSlotCount(i, -1); + return true; + } + } + return false; + } + else + { + // The slot is taken, try to top it up: + const cItem & DestSlot = Grid.GetSlot(a_DstSlotNum); + if (DestSlot.IsFullStack()) + { + return false; + } + for (int i = 0; i < ContentsWidth * ContentsHeight; i++) + { + if (m_Contents.GetSlot(i).IsStackableWith(DestSlot)) + { + if (cPluginManager::Get()->CallHookHopperPushingItem(*m_World, *this, i, a_Entity, a_DstSlotNum)) + { + // A plugin disagrees with the move + continue; + } + Grid.ChangeSlotCount(a_DstSlotNum, 1); + m_Contents.ChangeSlotCount(i, -1); + return true; + } + } + return false; + } +} + + + + |