summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Bindings/AllToLua.pkg1
-rw-r--r--src/Bindings/ManualBindings.cpp50
-rw-r--r--src/Bindings/ManualBindings_World.cpp2
-rw-r--r--src/Bindings/Plugin.h2
-rw-r--r--src/Bindings/PluginLua.cpp48
-rw-r--r--src/Bindings/PluginLua.h2
-rw-r--r--src/Bindings/PluginManager.cpp38
-rw-r--r--src/Bindings/PluginManager.h5
-rw-r--r--src/BlockEntities/BlockEntity.cpp2
-rw-r--r--src/BlockEntities/BrewingstandEntity.cpp309
-rw-r--r--src/BlockEntities/BrewingstandEntity.h136
-rw-r--r--src/BlockEntities/CMakeLists.txt2
-rw-r--r--src/Blocks/BlockBrewingStand.h8
-rw-r--r--src/BrewingRecipes.cpp287
-rw-r--r--src/BrewingRecipes.h55
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/Chunk.cpp53
-rw-r--r--src/Chunk.h8
-rw-r--r--src/ChunkMap.cpp33
-rw-r--r--src/ChunkMap.h9
-rw-r--r--src/Root.cpp8
-rw-r--r--src/Root.h3
-rw-r--r--src/UI/BrewingstandWindow.cpp70
-rw-r--r--src/UI/BrewingstandWindow.h31
-rw-r--r--src/UI/CMakeLists.txt2
-rw-r--r--src/UI/SlotArea.cpp238
-rw-r--r--src/UI/SlotArea.h30
-rw-r--r--src/UI/Window.h1
-rw-r--r--src/World.cpp18
-rw-r--r--src/World.h8
-rw-r--r--src/WorldStorage/NBTChunkSerializer.cpp17
-rw-r--r--src/WorldStorage/NBTChunkSerializer.h2
-rwxr-xr-xsrc/WorldStorage/WSSAnvil.cpp51
-rwxr-xr-xsrc/WorldStorage/WSSAnvil.h1
34 files changed, 1523 insertions, 9 deletions
diff --git a/src/Bindings/AllToLua.pkg b/src/Bindings/AllToLua.pkg
index 8f55eba07..991ed0ddd 100644
--- a/src/Bindings/AllToLua.pkg
+++ b/src/Bindings/AllToLua.pkg
@@ -98,6 +98,7 @@ $cfile "../Mobs/Monster.h"
$cfile "../BlockEntities/BlockEntity.h"
$cfile "../BlockEntities/BeaconEntity.h"
$cfile "../BlockEntities/BlockEntityWithItems.h"
+$cfile "../BlockEntities/BrewingstandEntity.h"
$cfile "../BlockEntities/ChestEntity.h"
$cfile "../BlockEntities/CommandBlockEntity.h"
$cfile "../BlockEntities/DropSpenserEntity.h"
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index 7e6839fdf..3a595c1d2 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -20,6 +20,7 @@
#include "../ClientHandle.h"
#include "../BlockArea.h"
#include "../BlockEntities/BeaconEntity.h"
+#include "../BlockEntities/BrewingstandEntity.h"
#include "../BlockEntities/ChestEntity.h"
#include "../BlockEntities/CommandBlockEntity.h"
#include "../BlockEntities/DispenserEntity.h"
@@ -2516,6 +2517,54 @@ static int tolua_cRoot_GetBuildSeriesName(lua_State * tolua_S)
+static int tolua_cRoot_GetBrewingRecipe(lua_State * tolua_S)
+{
+ cLuaState L(tolua_S);
+ if (
+ !L.CheckParamUserTable(1, "cRoot") ||
+ !L.CheckParamUserType (2, "const cItem") ||
+ !L.CheckParamUserType (3, "const cItem") ||
+ !L.CheckParamEnd (4)
+ )
+ {
+ return 0;
+ }
+
+ // Check the bottle param:
+ cItem * Bottle = nullptr;
+ L.GetStackValue(2, Bottle);
+ if (Bottle == nullptr)
+ {
+ LOGWARNING("cRoot:GetBrewingRecipe: the Bottle parameter is nil or missing.");
+ return 0;
+ }
+
+ cItem * Ingredient = nullptr;
+ L.GetStackValue(3, Ingredient);
+ if (Ingredient == nullptr)
+ {
+ LOGWARNING("cRoot:GetBrewingRecipe: the Ingredient parameter is nil or missing.");
+ return 0;
+ }
+
+ // Get the recipe for the input
+ cBrewingRecipes * BR = cRoot::Get()->GetBrewingRecipes();
+ const cBrewingRecipes::cRecipe * Recipe = BR->GetRecipeFrom(*Bottle, *Ingredient);
+ if (Recipe == nullptr)
+ {
+ // There is no such brewing recipe for this bottle and ingredient, return no value
+ return 0;
+ }
+
+ // Push the output item
+ L.Push(Recipe->Output.get());
+ return 1;
+}
+
+
+
+
+
static int tolua_cRoot_GetFurnaceRecipe(lua_State * tolua_S)
{
cLuaState L(tolua_S);
@@ -3320,6 +3369,7 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_function(tolua_S, "DoWithPlayerByUUID", DoWith <cRoot, cPlayer, &cRoot::DoWithPlayerByUUID>);
tolua_function(tolua_S, "ForEachPlayer", ForEach<cRoot, cPlayer, &cRoot::ForEachPlayer>);
tolua_function(tolua_S, "ForEachWorld", ForEach<cRoot, cWorld, &cRoot::ForEachWorld>);
+ tolua_function(tolua_S, "GetBrewingRecipe", tolua_cRoot_GetBrewingRecipe);
tolua_function(tolua_S, "GetBuildCommitID", tolua_cRoot_GetBuildCommitID);
tolua_function(tolua_S, "GetBuildDateTime", tolua_cRoot_GetBuildDateTime);
tolua_function(tolua_S, "GetBuildID", tolua_cRoot_GetBuildID);
diff --git a/src/Bindings/ManualBindings_World.cpp b/src/Bindings/ManualBindings_World.cpp
index c664329f9..20fe880c1 100644
--- a/src/Bindings/ManualBindings_World.cpp
+++ b/src/Bindings/ManualBindings_World.cpp
@@ -580,6 +580,7 @@ void cManualBindings::BindWorld(lua_State * tolua_S)
tolua_function(tolua_S, "ChunkStay", tolua_cWorld_ChunkStay);
tolua_function(tolua_S, "DoWithBlockEntityAt", DoWithXYZ<cWorld, cBlockEntity, &cWorld::DoWithBlockEntityAt>);
tolua_function(tolua_S, "DoWithBeaconAt", DoWithXYZ<cWorld, cBeaconEntity, &cWorld::DoWithBeaconAt>);
+ tolua_function(tolua_S, "DoWithBrewingstandAt", DoWithXYZ<cWorld, cBrewingstandEntity, &cWorld::DoWithBrewingstandAt>);
tolua_function(tolua_S, "DoWithChestAt", DoWithXYZ<cWorld, cChestEntity, &cWorld::DoWithChestAt>);
tolua_function(tolua_S, "DoWithDispenserAt", DoWithXYZ<cWorld, cDispenserEntity, &cWorld::DoWithDispenserAt>);
tolua_function(tolua_S, "DoWithDropSpenserAt", DoWithXYZ<cWorld, cDropSpenserEntity, &cWorld::DoWithDropSpenserAt>);
@@ -594,6 +595,7 @@ void cManualBindings::BindWorld(lua_State * tolua_S)
tolua_function(tolua_S, "FindAndDoWithPlayer", DoWith< cWorld, cPlayer, &cWorld::FindAndDoWithPlayer>);
tolua_function(tolua_S, "DoWithPlayerByUUID", DoWith< cWorld, cPlayer, &cWorld::DoWithPlayerByUUID>);
tolua_function(tolua_S, "ForEachBlockEntityInChunk", ForEachInChunk<cWorld, cBlockEntity, &cWorld::ForEachBlockEntityInChunk>);
+ tolua_function(tolua_S, "ForEachBrewingstandInChunk", ForEachInChunk<cWorld, cBrewingstandEntity, &cWorld::ForEachBrewingstandInChunk>);
tolua_function(tolua_S, "ForEachChestInChunk", ForEachInChunk<cWorld, cChestEntity, &cWorld::ForEachChestInChunk>);
tolua_function(tolua_S, "ForEachEntity", ForEach< cWorld, cEntity, &cWorld::ForEachEntity>);
tolua_function(tolua_S, "ForEachEntityInBox", ForEachInBox< cWorld, cEntity, &cWorld::ForEachEntityInBox>);
diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h
index 956e99dd9..b8fc97a06 100644
--- a/src/Bindings/Plugin.h
+++ b/src/Bindings/Plugin.h
@@ -45,6 +45,8 @@ public:
/** Calls the specified hook with the params given. Returns the bool that the hook callback returns. */
virtual bool OnBlockSpread (cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source) = 0;
virtual bool OnBlockToPickups (cWorld & a_World, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups) = 0;
+ virtual bool OnBrewingCompleting (cWorld & a_World, cBrewingstandEntity & a_BrewingstandEntity) = 0;
+ virtual bool OnBrewingCompleted (cWorld & a_World, cBrewingstandEntity & a_BrewingstandEntity) = 0;
virtual bool OnChat (cPlayer & a_Player, AString & a_Message) = 0;
virtual bool OnChunkAvailable (cWorld & a_World, int a_ChunkX, int a_ChunkZ) = 0;
virtual bool OnChunkGenerated (cWorld & a_World, int a_ChunkX, int a_ChunkZ, cChunkDesc * a_ChunkDesc) = 0;
diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp
index 50a0a387b..3038264d2 100644
--- a/src/Bindings/PluginLua.cpp
+++ b/src/Bindings/PluginLua.cpp
@@ -294,6 +294,54 @@ bool cPluginLua::OnBlockToPickups(cWorld & a_World, cEntity * a_Digger, int a_Bl
+bool cPluginLua::OnBrewingCompleted(cWorld & a_World, cBrewingstandEntity & a_Brewingstand)
+{
+ cCSLock Lock(m_CriticalSection);
+ if (!m_LuaState.IsValid())
+ {
+ return false;
+ }
+ bool res = false;
+ cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_BREWING_COMPLETED];
+ for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr)
+ {
+ m_LuaState.Call(static_cast<int>(**itr), &a_World, &a_Brewingstand, cLuaState::Return, res);
+ if (res)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cPluginLua::OnBrewingCompleting(cWorld & a_World, cBrewingstandEntity & a_Brewingstand)
+{
+ cCSLock Lock(m_CriticalSection);
+ if (!m_LuaState.IsValid())
+ {
+ return false;
+ }
+ bool res = false;
+ cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_BREWING_COMPLETING];
+ for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr)
+ {
+ m_LuaState.Call(static_cast<int>(**itr), &a_World, &a_Brewingstand, cLuaState::Return, res);
+ if (res)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
bool cPluginLua::OnChat(cPlayer & a_Player, AString & a_Message)
{
cCSLock Lock(m_CriticalSection);
diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h
index 1312103b8..59e56c0e7 100644
--- a/src/Bindings/PluginLua.h
+++ b/src/Bindings/PluginLua.h
@@ -105,6 +105,8 @@ public:
virtual bool OnBlockSpread (cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source) override;
virtual bool OnBlockToPickups (cWorld & a_World, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups) override;
+ virtual bool OnBrewingCompleting (cWorld & a_World, cBrewingstandEntity & a_BrewingstandEntity) override;
+ virtual bool OnBrewingCompleted (cWorld & a_World, cBrewingstandEntity & a_BrewingstandEntity) override;
virtual bool OnChat (cPlayer & a_Player, AString & a_Message) override;
virtual bool OnChunkAvailable (cWorld & a_World, int a_ChunkX, int a_ChunkZ) override;
virtual bool OnChunkGenerated (cWorld & a_World, int a_ChunkX, int a_ChunkZ, cChunkDesc * a_ChunkDesc) override;
diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp
index 72e031b33..4d291f164 100644
--- a/src/Bindings/PluginManager.cpp
+++ b/src/Bindings/PluginManager.cpp
@@ -269,6 +269,44 @@ bool cPluginManager::CallHookBlockToPickups(
+bool cPluginManager::CallHookBrewingCompleted(cWorld & a_World, cBrewingstandEntity & a_Brewingstand)
+{
+ FIND_HOOK(HOOK_BREWING_COMPLETED);
+ VERIFY_HOOK;
+
+ for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr)
+ {
+ if ((*itr)->OnBrewingCompleted(a_World, a_Brewingstand))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cPluginManager::CallHookBrewingCompleting(cWorld & a_World, cBrewingstandEntity & a_Brewingstand)
+{
+ FIND_HOOK(HOOK_BREWING_COMPLETING);
+ VERIFY_HOOK;
+
+ for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr)
+ {
+ if ((*itr)->OnBrewingCompleting(a_World, a_Brewingstand))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
bool cPluginManager::CallHookChat(cPlayer & a_Player, AString & a_Message)
{
// Check if the message contains a command, execute it:
diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h
index d7e3b8dfe..58ec74f53 100644
--- a/src/Bindings/PluginManager.h
+++ b/src/Bindings/PluginManager.h
@@ -10,6 +10,7 @@
// fwd:
class cBlockEntityWithItems;
+class cBrewingstandEntity;
class cChunkDesc;
class cClientHandle;
class cCommandOutputCallback;
@@ -75,6 +76,8 @@ public:
{
HOOK_BLOCK_SPREAD,
HOOK_BLOCK_TO_PICKUPS,
+ HOOK_BREWING_COMPLETING,
+ HOOK_BREWING_COMPLETED,
HOOK_CHAT,
HOOK_CHUNK_AVAILABLE,
HOOK_CHUNK_GENERATED,
@@ -193,6 +196,8 @@ public:
// Calls for individual hooks. Each returns false if the action is to continue or true if the plugin wants to abort
bool CallHookBlockSpread (cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source);
bool CallHookBlockToPickups (cWorld & a_World, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups);
+ bool CallHookBrewingCompleting (cWorld & a_World, cBrewingstandEntity & a_Brewingstand);
+ bool CallHookBrewingCompleted (cWorld & a_World, cBrewingstandEntity & a_Brewingstand);
bool CallHookChat (cPlayer & a_Player, AString & a_Message);
bool CallHookChunkAvailable (cWorld & a_World, int a_ChunkX, int a_ChunkZ);
bool CallHookChunkGenerated (cWorld & a_World, int a_ChunkX, int a_ChunkZ, cChunkDesc * a_ChunkDesc);
diff --git a/src/BlockEntities/BlockEntity.cpp b/src/BlockEntities/BlockEntity.cpp
index c59198e79..0b69830f6 100644
--- a/src/BlockEntities/BlockEntity.cpp
+++ b/src/BlockEntities/BlockEntity.cpp
@@ -6,6 +6,7 @@
#include "Globals.h"
#include "BeaconEntity.h"
#include "BlockEntity.h"
+#include "BrewingstandEntity.h"
#include "ChestEntity.h"
#include "CommandBlockEntity.h"
#include "DispenserEntity.h"
@@ -36,6 +37,7 @@ cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE
case E_BLOCK_ENDER_CHEST: return new cEnderChestEntity (a_BlockX, a_BlockY, a_BlockZ, a_World);
case E_BLOCK_FLOWER_POT: return new cFlowerPotEntity (a_BlockX, a_BlockY, a_BlockZ, a_World);
case E_BLOCK_FURNACE: return new cFurnaceEntity (a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_World);
+ case E_BLOCK_BREWING_STAND: return new cBrewingstandEntity(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_World);
case E_BLOCK_HEAD: return new cMobHeadEntity (a_BlockX, a_BlockY, a_BlockZ, a_World);
case E_BLOCK_HOPPER: return new cHopperEntity (a_BlockX, a_BlockY, a_BlockZ, a_World);
case E_BLOCK_MOB_SPAWNER: return new cMobSpawnerEntity (a_BlockX, a_BlockY, a_BlockZ, a_World);
diff --git a/src/BlockEntities/BrewingstandEntity.cpp b/src/BlockEntities/BrewingstandEntity.cpp
new file mode 100644
index 000000000..e34297393
--- /dev/null
+++ b/src/BlockEntities/BrewingstandEntity.cpp
@@ -0,0 +1,309 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "BrewingstandEntity.h"
+#include "../Bindings/PluginManager.h"
+#include "../UI/BrewingstandWindow.h"
+#include "../Entities/Player.h"
+#include "../Root.h"
+#include "../Chunk.h"
+
+
+
+
+
+
+
+
+
+
+
+
+cBrewingstandEntity::cBrewingstandEntity(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cWorld * a_World) :
+ super(a_BlockType, a_BlockX, a_BlockY, a_BlockZ, ContentsWidth, ContentsHeight, a_World),
+ m_BlockMeta(a_BlockMeta),
+ m_IsDestroyed(false),
+ m_IsBrewing(false),
+ m_TimeBrewed(0)
+{
+ m_Contents.AddListener(*this);
+ for (int i = 0; i < 3; i++)
+ {
+ m_Results[i] = *(new cItem());
+ }
+}
+
+
+
+
+
+cBrewingstandEntity::~cBrewingstandEntity()
+{
+ // Tell window its owner is destroyed
+ cWindow * Window = GetWindow();
+ if (Window != nullptr)
+ {
+ Window->OwnerDestroyed();
+ }
+}
+
+
+
+
+
+void cBrewingstandEntity::UsedBy(cPlayer * a_Player)
+{
+ cWindow * Window = GetWindow();
+ if (Window == nullptr)
+ {
+ OpenWindow(new cBrewingstandWindow(m_PosX, m_PosY, m_PosZ, this));
+ Window = GetWindow();
+ }
+
+ if (Window != nullptr)
+ {
+ if (a_Player->GetWindow() != Window)
+ {
+ a_Player->OpenWindow(Window);
+ }
+ }
+
+ if (m_IsBrewing)
+ {
+ BroadcastProgress(0, m_NeedBrewingTime - m_TimeBrewed);
+ }
+ else
+ {
+ BroadcastProgress(0, 0);
+ }
+}
+
+
+
+
+
+bool cBrewingstandEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
+{
+ UNUSED(a_Dt);
+
+ if (!m_IsBrewing)
+ {
+ return false;
+ }
+
+ const cBrewingRecipes::cRecipe * Recipe = nullptr;
+ // The necessary brewing time, has been reached
+ if (m_TimeBrewed >= m_NeedBrewingTime)
+ {
+ BroadcastProgress(0, 0);
+ m_IsBrewing = false;
+ m_TimeBrewed = 0;
+
+ // Return if the hook has been canceled
+ if (cPluginManager::Get()->CallHookBrewingCompleting(*m_World, *this))
+ {
+ return false;
+ }
+
+ // Decrease item count, full stacks are allowed in the ingredient slot
+ cItem Ingredient = m_Contents.GetSlot(bsIngredient);
+ Ingredient.m_ItemCount -= 1;
+ m_Contents.SetSlot(bsIngredient, Ingredient);
+
+ // Loop over all bottle slots and update available bottles
+ for (int i = 0; i < 3; i++)
+ {
+ if (m_Contents.GetSlot(i).IsEmpty() || (m_CurrentBrewingRecipes[i] == nullptr))
+ {
+ continue;
+ }
+
+ Recipe = m_CurrentBrewingRecipes[i];
+ m_Contents.SetSlot(i, Recipe->Output->CopyOne());
+ }
+
+ // Brewing process completed
+ cPluginManager::Get()->CallHookBrewingCompleted(*m_World, *this);
+
+ return true;
+ }
+
+ m_TimeBrewed++;
+ UpdateProgressBars(false);
+ return false;
+}
+
+
+
+
+
+
+void cBrewingstandEntity::SendTo(cClientHandle & a_Client)
+{
+ // Nothing needs to be sent
+ UNUSED(a_Client);
+}
+
+
+
+
+
+void cBrewingstandEntity::BroadcastProgress(short a_ProgressbarID, short a_Value)
+{
+ cWindow * Window = GetWindow();
+ if (Window != nullptr)
+ {
+ Window->SetProperty(a_ProgressbarID, a_Value);
+ }
+}
+
+
+
+
+
+
+void cBrewingstandEntity::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
+{
+ super::OnSlotChanged(a_ItemGrid, a_SlotNum);
+
+ if (m_IsDestroyed)
+ {
+ return;
+ }
+
+ ASSERT(a_ItemGrid == &m_Contents);
+
+ // Check if still a item is in the ingredient slot
+ if (GetSlot(bsIngredient).IsEmpty())
+ {
+ if (m_IsBrewing)
+ {
+ // Cancel brewing
+ BroadcastProgress(0, 0);
+ m_IsBrewing = false;
+ m_TimeBrewed = 0;
+ }
+ return;
+ }
+
+ // Recheck the bottles
+ cBrewingRecipes * BR = cRoot::Get()->GetBrewingRecipes();
+ const cBrewingRecipes::cRecipe * Recipe = nullptr;
+ bool Stop = true;
+ for (int i = 0; i < 3; i++)
+ {
+ if (GetSlot(i).IsEmpty())
+ {
+ m_CurrentBrewingRecipes[i] = nullptr;
+ m_Results[i].Clear();
+ continue;
+ }
+
+ if (m_CurrentBrewingRecipes[i] != nullptr)
+ {
+ Recipe = m_CurrentBrewingRecipes[i];
+ if (Recipe->Ingredient->IsEqual(GetSlot(bsIngredient)) && Recipe->Input->IsEqual(GetSlot(i)))
+ {
+ Stop = false;
+ continue;
+ }
+ }
+
+ Recipe = BR->GetRecipeFrom(m_Contents.GetSlot(i), m_Contents.GetSlot(bsIngredient));
+ if (Recipe != nullptr)
+ {
+ // Found a brewing recipe for the items
+ m_CurrentBrewingRecipes[i] = Recipe;
+ m_Results[i] = Recipe->Output->CopyOne();
+ Stop = false;
+ }
+ }
+
+ if (Stop)
+ {
+ if (m_IsBrewing)
+ {
+ // Cancel brewing
+ BroadcastProgress(0, 0);
+ m_IsBrewing = false;
+ m_TimeBrewed = 0;
+ }
+ return;
+ }
+
+ // Start brewing process, if not running
+ if (!m_IsBrewing)
+ {
+ m_IsBrewing = true;
+ }
+}
+
+
+
+
+
+void cBrewingstandEntity::UpdateProgressBars(bool a_ForceUpdate)
+{
+ /** Sending an update every 3th tick, using a higher value lets look the progressbar ugly */
+ if (!a_ForceUpdate && (m_World->GetWorldAge() % 3 != 0))
+ {
+ return;
+ }
+
+ BroadcastProgress(0, m_NeedBrewingTime - m_TimeBrewed);
+}
+
+
+
+
+
+void cBrewingstandEntity::setTimeBrewed(short a_TimeBrewed)
+{
+ m_TimeBrewed = a_TimeBrewed;
+}
+
+
+
+
+
+void cBrewingstandEntity::ContinueBrewing(void)
+{
+ // Continue brewing if number is greater than 0
+ if (m_TimeBrewed > 0)
+ {
+ m_IsBrewing = true;
+ }
+}
+
+
+
+
+
+void cBrewingstandEntity::GetRecipes(void)
+{
+ if (GetSlot(3).IsEmpty())
+ {
+ return;
+ }
+
+ cBrewingRecipes * BR = cRoot::Get()->GetBrewingRecipes();
+ const cBrewingRecipes::cRecipe * Recipe = nullptr;
+ for (int i = 0; i < 3; i++)
+ {
+ if (GetSlot(i).IsEmpty())
+ {
+ continue;
+ }
+ Recipe = BR->GetRecipeFrom(GetSlot(i), GetSlot(bsIngredient));
+ if (Recipe != nullptr)
+ {
+ m_CurrentBrewingRecipes[i] = Recipe;
+ m_Results[i] = Recipe->Output->CopyOne();
+ }
+ }
+}
+
+
+
+
+
diff --git a/src/BlockEntities/BrewingstandEntity.h b/src/BlockEntities/BrewingstandEntity.h
new file mode 100644
index 000000000..a895c4bde
--- /dev/null
+++ b/src/BlockEntities/BrewingstandEntity.h
@@ -0,0 +1,136 @@
+
+#pragma once
+
+#include "BlockEntityWithItems.h"
+#include "../BrewingRecipes.h"
+#include "../Root.h"
+
+
+
+
+class cClientHandle;
+
+
+
+
+
+// tolua_begin
+class cBrewingstandEntity :
+ public cBlockEntityWithItems
+{
+ typedef cBlockEntityWithItems super;
+
+public:
+ enum
+ {
+ bsLeftBottle = 0, // Left bottle slot number
+ bsMiddleBottle = 1, // Middle bottle slot number
+ bsRightBottle = 2, // Right bottle slot number
+ bsIngredient = 3, // Top ingredient slot number
+
+ ContentsWidth = 4,
+ ContentsHeight = 1,
+ };
+
+ // tolua_end
+
+ BLOCKENTITY_PROTODEF(cBrewingstandEntity)
+
+ /** Constructor used for normal operation */
+ cBrewingstandEntity(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cWorld * a_World);
+
+ virtual ~cBrewingstandEntity();
+
+ // cBlockEntity overrides:
+ virtual void SendTo(cClientHandle & a_Client) override;
+ virtual bool Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
+ virtual void UsedBy(cPlayer * a_Player) override;
+ virtual void Destroy() override
+ {
+ m_IsDestroyed = true;
+ super::Destroy();
+ }
+
+ // tolua_begin
+
+ /** Returns the time until the current items finishes brewing, in ticks */
+ short GetBrewingTimeLeft(void) const { return m_NeedBrewingTime - m_TimeBrewed; }
+
+ /** Returns the time that the current items has been brewing, in ticks */
+ short GetTimeBrewed(void) { return m_TimeBrewed; }
+
+ /** Returns the item in the left bottle slot */
+ const cItem & GetLeftBottleSlot(void) const { return GetSlot(bsLeftBottle); }
+
+ /** Returns the item in the middle bottle slot */
+ const cItem & GetMiddleBottleSlot(void) const { return GetSlot(bsMiddleBottle); }
+
+ /** Returns the item in the right bottle slot */
+ const cItem & GetRightBottleSlot(void) const { return GetSlot(bsRightBottle); }
+
+ /** Returns the item in the ingredient slot */
+ const cItem & GetIndgredientSlot(void) const { return GetSlot(bsIngredient); }
+
+ /** Get the expected result item for the given slot number */
+ const cItem & GetResultItem(int a_SlotNumber) { return m_Results[a_SlotNumber]; }
+
+ /** Sets the item in the left bottle slot */
+ void SetLeftBottleSlot(const cItem & a_Item) { SetSlot(bsLeftBottle, a_Item); }
+
+ /** Sets the item in the middle bottle slot */
+ void SetMiddleBottleSlot(const cItem & a_Item) { SetSlot(bsMiddleBottle, a_Item); }
+
+ /** Sets the item in the right bottle slot */
+ void SetRightBottleSlot(const cItem & a_Item) { SetSlot(bsRightBottle, a_Item); }
+
+ /** Sets the item in the ingredient slot */
+ void SetIngredientSlot(const cItem & a_Item) { SetSlot(bsIngredient, a_Item); }
+
+ // tolua_end
+
+ /** Sets the current brewing time. Will be called if the brewing stand gets loaded from the world. */
+ void setTimeBrewed(short a_TimeBrewed);
+
+ /** Starts the brewing proccess. Will be called if the brewing stand gets loaded from the world. */
+ void ContinueBrewing(void);
+
+ /** Gets the recipes. Will be called if the brewing stand gets loaded from the world. */
+ void GetRecipes(void);
+protected:
+
+ /** Block meta of the block currently represented by this entity */
+ NIBBLETYPE m_BlockMeta;
+
+ /** Set to true when the brewing stand entity has been destroyed to prevent the block being set again */
+ bool m_IsDestroyed;
+
+ /** Set to true if the brewing stand is brewing an item */
+ bool m_IsBrewing;
+
+ /** Brewing time is 400 ticks */
+ const short m_NeedBrewingTime = 400;
+
+ /** Store the current brewing recipes */
+ const cBrewingRecipes::cRecipe * m_CurrentBrewingRecipes[3] = {};
+
+ /** Result items for the bottle inputs */
+ cItem m_Results[3] = {};
+
+ /** Amount of ticks that the current item has been brewed */
+ short m_TimeBrewed;
+
+ /** Sends the specified progressbar value to all clients of the window */
+ void BroadcastProgress(short a_ProgressbarID, short a_Value);
+
+ // /** Broadcasts progressbar updates, if needed */
+ void UpdateProgressBars(bool a_ForceUpdate = false);
+
+ // cItemGrid::cListener overrides:
+ virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) override;
+
+} ; // tolua_export
+
+
+
+
+
diff --git a/src/BlockEntities/CMakeLists.txt b/src/BlockEntities/CMakeLists.txt
index 0d1776eb5..931484be0 100644
--- a/src/BlockEntities/CMakeLists.txt
+++ b/src/BlockEntities/CMakeLists.txt
@@ -7,6 +7,7 @@ include_directories ("${PROJECT_SOURCE_DIR}/../")
SET (SRCS
BeaconEntity.cpp
BlockEntity.cpp
+ BrewingstandEntity.cpp
ChestEntity.cpp
CommandBlockEntity.cpp
DispenserEntity.cpp
@@ -27,6 +28,7 @@ SET (HDRS
BeaconEntity.h
BlockEntity.h
BlockEntityWithItems.h
+ BrewingstandEntity.h
ChestEntity.h
CommandBlockEntity.h
DispenserEntity.h
diff --git a/src/Blocks/BlockBrewingStand.h b/src/Blocks/BlockBrewingStand.h
index f68f9d8af..0ab5c8eba 100644
--- a/src/Blocks/BlockBrewingStand.h
+++ b/src/Blocks/BlockBrewingStand.h
@@ -1,18 +1,18 @@
#pragma once
+#include "BlockEntity.h"
#include "BlockHandler.h"
-
-
+#include "MetaRotator.h"
class cBlockBrewingStandHandler :
- public cBlockHandler
+ public cMetaRotator<cBlockEntityHandler, 0x07, 0x02, 0x05, 0x03, 0x04>
{
public:
cBlockBrewingStandHandler(BLOCKTYPE a_BlockType)
- : cBlockHandler(a_BlockType)
+ : cMetaRotator<cBlockEntityHandler, 0x07, 0x02, 0x05, 0x03, 0x04>(a_BlockType)
{
}
diff --git a/src/BrewingRecipes.cpp b/src/BrewingRecipes.cpp
new file mode 100644
index 000000000..3ee39c8af
--- /dev/null
+++ b/src/BrewingRecipes.cpp
@@ -0,0 +1,287 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "BrewingRecipes.h"
+#include "Item.h"
+
+#include <fstream>
+
+#define BREWING_RECIPE_FILE "brewing.txt"
+
+
+
+
+
+typedef std::vector<std::unique_ptr<cBrewingRecipes::cRecipe>> RecipeList;
+
+
+
+
+
+struct cBrewingRecipes::sBrewingRecipeState
+{
+ RecipeList Recipes;
+};
+
+
+
+
+
+cBrewingRecipes::cBrewingRecipes()
+ : m_pState(new sBrewingRecipeState)
+{
+ ReloadRecipes();
+}
+
+
+
+
+
+cBrewingRecipes::~cBrewingRecipes()
+{
+ ClearRecipes();
+}
+
+
+
+
+
+void cBrewingRecipes::ReloadRecipes(void)
+{
+ ClearRecipes();
+ LOGD("Loading brewing recipes...");
+
+ std::ifstream f(BREWING_RECIPE_FILE, std::ios::in);
+ if (!f.good())
+ {
+ LOG("Could not open the brewing recipes file \"%s\". No brewing recipes are available.", BREWING_RECIPE_FILE);
+ return;
+ }
+
+ unsigned int LineNum = 0;
+ AString ParsingLine;
+
+ while (std::getline(f, ParsingLine))
+ {
+ LineNum++;
+ // Remove comments from the line:
+ size_t FirstCommentSymbol = ParsingLine.find('#');
+ if (FirstCommentSymbol != AString::npos)
+ {
+ ParsingLine.erase(ParsingLine.begin() += static_cast<long>(FirstCommentSymbol), ParsingLine.end());
+ }
+
+ if (ParsingLine.empty())
+ {
+ continue;
+ }
+ AddRecipeFromLine(ParsingLine, LineNum);
+ } // while (getline(ParsingLine))
+
+ LOG("Loaded " SIZE_T_FMT " brewing recipes", m_pState->Recipes.size());
+}
+
+
+
+
+
+void cBrewingRecipes::AddRecipeFromLine(const AString & a_Line, unsigned int a_LineNum)
+{
+ AString Line(a_Line);
+ Line.erase(std::remove_if(Line.begin(), Line.end(), isspace), Line.end());
+
+ short InputDamage;
+ short OutputDamage;
+
+ std::unique_ptr<cItem> InputItem = cpp14::make_unique<cItem>();
+ std::unique_ptr<cItem> IngredientItem = cpp14::make_unique<cItem>();
+ std::unique_ptr<cItem> OutputItem = cpp14::make_unique<cItem>();
+
+ const AStringVector & InputAndIngredient = StringSplit(Line, "+");
+
+ if (InputAndIngredient.size() != 2)
+ {
+ LOGWARNING("brewing.txt: line %d: A line with '+' was expected", a_LineNum);
+ LOGINFO("Offending line: \"%s\"", a_Line.c_str());
+ return;
+ }
+
+ const AStringVector & IngredientAndOutput = StringSplit(InputAndIngredient[1].c_str(), "=");
+ if (IngredientAndOutput.size() != 2)
+ {
+ LOGWARNING("brewing.txt: line %d: A line with '=' was expected", a_LineNum);
+ LOGINFO("Offending line: \"%s\"", a_Line.c_str());
+ return;
+ }
+
+ if (!ParseItem(IngredientAndOutput[0], *IngredientItem))
+ {
+ LOGWARNING("brewing.txt: Parsing of the item didn't worked.");
+ LOGINFO("Offending line: \"%s\"", a_Line.c_str());
+ return;
+ }
+
+ if (!StringToInteger<short>(InputAndIngredient[0], InputDamage))
+ {
+ LOGWARNING("brewing.txt: line %d: Cannot parse the damage value for the input item\"%s\".", a_LineNum, InputAndIngredient[0].c_str());
+ LOGINFO("Offending line: \"%s\"", a_Line.c_str());
+ return;
+ }
+
+ if (!StringToInteger<short>(IngredientAndOutput[1], OutputDamage))
+ {
+ LOGWARNING("brewing.txt: line %d: Cannot parse the damage value for the output item\"%s\".", a_LineNum, IngredientAndOutput[1].c_str());
+ LOGINFO("Offending line: \"%s\"", a_Line.c_str());
+ return;
+ }
+
+ // The items has always the same type
+ InputItem->m_ItemType = E_ITEM_POTION;
+ InputItem->m_ItemDamage = InputDamage;
+
+ OutputItem->m_ItemType = E_ITEM_POTION;
+ OutputItem->m_ItemDamage = OutputDamage;
+
+ std::unique_ptr<cRecipe> Recipe = cpp14::make_unique<cRecipe>();
+ Recipe->Input = std::move(InputItem);
+ Recipe->Output = std::move(OutputItem);
+ Recipe->Ingredient = std::move(IngredientItem);
+ m_pState->Recipes.push_back(std::move(Recipe));
+}
+
+
+
+
+
+bool cBrewingRecipes::ParseItem(const AString & a_String, cItem & a_Item)
+{
+ return StringToItem(a_String, a_Item);
+}
+
+
+
+
+
+void cBrewingRecipes::ClearRecipes(void)
+{
+ m_pState->Recipes.clear();
+}
+
+
+
+
+
+const cBrewingRecipes::cRecipe * cBrewingRecipes::GetRecipeFrom(const cItem & a_Input, const cItem & a_Ingredient) const
+{
+ for (auto & Recipe : m_pState->Recipes)
+ {
+ if ((Recipe->Input->IsEqual(a_Input)) && (Recipe->Ingredient->IsEqual(a_Ingredient)))
+ {
+ return Recipe.get();
+ }
+ }
+
+ // Check for gunpowder
+ if (a_Ingredient.m_ItemType == E_ITEM_GUNPOWDER)
+ {
+ if (a_Input.m_ItemDamage & 0x2000)
+ {
+ // Create new recipe and add it to list
+ std::unique_ptr<cItem> InputItem = cpp14::make_unique<cItem>();
+ std::unique_ptr<cItem> IngredientItem = cpp14::make_unique<cItem>();
+ std::unique_ptr<cItem> OutputItem = cpp14::make_unique<cItem>();
+
+ InputItem->m_ItemType = E_ITEM_POTION;
+ InputItem->m_ItemDamage = a_Input.m_ItemDamage;
+ OutputItem->m_ItemType = E_ITEM_POTION;
+ OutputItem->m_ItemDamage = a_Input.m_ItemDamage + 8192;
+ IngredientItem->m_ItemType = E_ITEM_GUNPOWDER;
+
+ std::unique_ptr<cRecipe> Recipe = cpp14::make_unique<cRecipe>();
+ Recipe->Input = std::move(InputItem);
+ Recipe->Output = std::move(OutputItem);
+ Recipe->Ingredient = std::move(IngredientItem);
+ m_pState->Recipes.push_back(std::move(Recipe));
+ return Recipe.get();
+ }
+ return nullptr;
+ }
+
+ // Check for splash potion
+ if (a_Input.m_ItemDamage & 0x4000)
+ {
+ const std::unique_ptr<cRecipe> * FoundRecipe = nullptr;
+ // Search for the drinkable potion, the ingredients are the same
+ short SplashItemDamage = a_Input.m_ItemDamage - 8192;
+
+ for (auto & Recipe : m_pState->Recipes)
+ {
+ if ((Recipe->Input->m_ItemDamage == SplashItemDamage) && (Recipe->Ingredient->IsEqual(a_Ingredient)))
+ {
+ FoundRecipe = &Recipe;
+ break;
+ }
+ }
+
+ if (FoundRecipe == nullptr)
+ {
+ return nullptr;
+ }
+
+ // Create new recipe and add it to list
+ std::unique_ptr<cItem> InputItem = cpp14::make_unique<cItem>();
+ std::unique_ptr<cItem> IngredientItem = cpp14::make_unique<cItem>();
+ std::unique_ptr<cItem> OutputItem = cpp14::make_unique<cItem>();
+
+ InputItem->m_ItemType = E_ITEM_POTION;
+ InputItem->m_ItemDamage = a_Input.m_ItemDamage;
+ OutputItem->m_ItemType = E_ITEM_POTION;
+ OutputItem->m_ItemDamage = (*FoundRecipe)->Output->m_ItemDamage + 8192;
+ IngredientItem->m_ItemType = (*FoundRecipe)->Ingredient->m_ItemType;
+
+ std::unique_ptr<cRecipe> Recipe = cpp14::make_unique<cRecipe>();
+ Recipe->Input = std::move(InputItem);
+ Recipe->Output = std::move(OutputItem);
+ Recipe->Ingredient = std::move(IngredientItem);
+ m_pState->Recipes.push_back(std::move(Recipe));
+ return Recipe.get();
+ }
+ return nullptr;
+}
+
+
+
+
+
+
+bool cBrewingRecipes::IsIngredient(const cItem & a_Ingredient) const
+{
+ // Check for gunpowder
+ if (a_Ingredient.m_ItemType == E_ITEM_GUNPOWDER)
+ {
+ return true;
+ }
+
+ for (auto & Recipe : m_pState->Recipes)
+ {
+ if (Recipe->Ingredient->IsEqual(a_Ingredient))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cBrewingRecipes::IsBottle(const cItem & a_Item) const
+{
+ return (a_Item.m_ItemType == E_ITEM_POTION);
+}
+
+
+
+
+
diff --git a/src/BrewingRecipes.h b/src/BrewingRecipes.h
new file mode 100644
index 000000000..82fb0b822
--- /dev/null
+++ b/src/BrewingRecipes.h
@@ -0,0 +1,55 @@
+
+#pragma once
+
+
+
+
+
+class cItem;
+
+
+
+
+
+class cBrewingRecipes
+{
+public:
+ cBrewingRecipes(void);
+ ~cBrewingRecipes();
+
+ void ReloadRecipes(void);
+
+ struct cRecipe
+ {
+ cRecipe() {}
+ cRecipe(cRecipe &&) {}
+
+ cRecipe(const cRecipe&) = delete;
+ cRecipe & operator=(const cRecipe&) = delete;
+
+ std::unique_ptr<cItem> Input;
+ std::unique_ptr<cItem> Output;
+ std::unique_ptr<cItem> Ingredient;
+ };
+
+ /** Returns a recipe for the specified input, nullptr if no recipe found */
+ const cRecipe * GetRecipeFrom(const cItem & a_Input, const cItem & a_Ingredient) const;
+
+ /** Returns true if the item is a ingredient, false if not. */
+ bool IsIngredient(const cItem & a_Ingredient) const;
+
+ /** Returns true if the item is a bottle / potion, false if not. */
+ bool IsBottle(const cItem & a_Bottle) const;
+private:
+ void ClearRecipes(void);
+
+ /** Parses the recipe contained in the line, adds it to m_pState's recipes.
+ Logs a warning to the console on input error. */
+ void AddRecipeFromLine(const AString & a_Line, unsigned int a_LineNum);
+
+ /** Parses an item string, returns true if successful. */
+ bool ParseItem(const AString & a_String, cItem & a_Item);
+
+ struct sBrewingRecipeState;
+ std::unique_ptr<sBrewingRecipeState> m_pState;
+};
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d37ba5419..9bc756eef 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -18,6 +18,7 @@ SET (SRCS
BlockArea.cpp
BlockID.cpp
BlockInfo.cpp
+ BrewingRecipes.cpp
Broadcaster.cpp
BoundingBox.cpp
ByteBuffer.cpp
@@ -84,6 +85,7 @@ SET (HDRS
BlockInServerPluginInterface.h
BlockInfo.h
BlockTracer.h
+ BrewingRecipes.h
Broadcaster.h
BoundingBox.h
BuildInfo.h
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index 39a5a6053..3e4a497be 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -13,6 +13,7 @@
#include "zlib/zlib.h"
#include "Defines.h"
#include "BlockEntities/BeaconEntity.h"
+#include "BlockEntities/BrewingstandEntity.h"
#include "BlockEntities/ChestEntity.h"
#include "BlockEntities/DispenserEntity.h"
#include "BlockEntities/DropperEntity.h"
@@ -1359,6 +1360,7 @@ void cChunk::CreateBlockEntities(void)
case E_BLOCK_JUKEBOX:
case E_BLOCK_FLOWER_POT:
case E_BLOCK_MOB_SPAWNER:
+ case E_BLOCK_BREWING_STAND:
{
if (!HasBlockEntityAt(x + m_PosX * Width, y, z + m_PosZ * Width))
{
@@ -1491,6 +1493,7 @@ void cChunk::SetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType,
case E_BLOCK_JUKEBOX:
case E_BLOCK_FLOWER_POT:
case E_BLOCK_MOB_SPAWNER:
+ case E_BLOCK_BREWING_STAND:
{
AddBlockEntity(cBlockEntity::CreateByBlockType(a_BlockType, a_BlockMeta, WorldPos.x, WorldPos.y, WorldPos.z, m_World));
break;
@@ -2056,6 +2059,24 @@ bool cChunk::ForEachBlockEntity(cBlockEntityCallback & a_Callback)
+bool cChunk::ForEachBrewingstand(cBrewingstandCallback & a_Callback)
+{
+ // The blockentity list is locked by the parent chunkmap's CS
+ for (cBlockEntityList::iterator itr = m_BlockEntities.begin(), itr2 = itr; itr != m_BlockEntities.end(); itr = itr2)
+ {
+ ++itr2;
+ if (a_Callback.Item(reinterpret_cast<cBrewingstandEntity *>(*itr)))
+ {
+ return false;
+ }
+ } // for itr - m_BlockEntitites[]
+ return true;
+}
+
+
+
+
+
bool cChunk::ForEachChest(cChestCallback & a_Callback)
{
// The blockentity list is locked by the parent chunkmap's CS
@@ -2271,6 +2292,38 @@ bool cChunk::DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCal
+bool cChunk::DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback & a_Callback)
+{
+ // The blockentity list is locked by the parent chunkmap's CS
+ for (cBlockEntityList::iterator itr = m_BlockEntities.begin(), itr2 = itr; itr != m_BlockEntities.end(); itr = itr2)
+ {
+ ++itr2;
+ if (((*itr)->GetPosX() != a_BlockX) || ((*itr)->GetPosY() != a_BlockY) || ((*itr)->GetPosZ() != a_BlockZ))
+ {
+ continue;
+ }
+ if ((*itr)->GetBlockType() != E_BLOCK_BREWING_STAND)
+ {
+ // There is a block entity here, but of different type. No other block entity can be here, so we can safely bail out
+ return false;
+ }
+
+ // The correct block entity is here
+ if (a_Callback.Item(reinterpret_cast<cBrewingstandEntity *>(*itr)))
+ {
+ return false;
+ }
+ return true;
+ } // for itr - m_BlockEntitites[]
+
+ // Not found:
+ return false;
+}
+
+
+
+
+
bool cChunk::DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback)
{
// The blockentity list is locked by the parent chunkmap's CS
diff --git a/src/Chunk.h b/src/Chunk.h
index 6316f6910..60fbafa94 100644
--- a/src/Chunk.h
+++ b/src/Chunk.h
@@ -31,6 +31,7 @@ class MTRand;
class cPlayer;
class cChunkMap;
class cBeaconEntity;
+class cBrewingstandEntity;
class cBoundingBox;
class cChestEntity;
class cChunkDataCallback;
@@ -54,6 +55,7 @@ class cSetChunkData;
typedef std::list<cClientHandle *> cClientHandleList;
typedef cItemCallback<cEntity> cEntityCallback;
typedef cItemCallback<cBeaconEntity> cBeaconCallback;
+typedef cItemCallback<cBrewingstandEntity> cBrewingstandCallback;
typedef cItemCallback<cChestEntity> cChestCallback;
typedef cItemCallback<cDispenserEntity> cDispenserCallback;
typedef cItemCallback<cFurnaceEntity> cFurnaceCallback;
@@ -256,6 +258,9 @@ public:
/** Calls the callback for each block entity; returns true if all block entities processed, false if the callback aborted by returning true */
bool ForEachBlockEntity(cBlockEntityCallback & a_Callback); // Lua-accessible
+ /** Calls the callback for each brewingstand; returns true if all brewingstands processed, false if the callback aborted by returning true */
+ bool ForEachBrewingstand(cBrewingstandCallback & a_Callback); // Lua-accessible
+
/** Calls the callback for each chest; returns true if all chests processed, false if the callback aborted by returning true */
bool ForEachChest(cChestCallback & a_Callback); // Lua-accessible
@@ -279,6 +284,9 @@ public:
/** Calls the callback for the beacon at the specified coords; returns false if there's no beacon at those coords, true if found */
bool DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback & a_Callback); // Lua-acessible
+ /** Calls the callback for the brewingstand at the specified coords; returns false if there's no brewingstand at those coords, true if found */
+ bool DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback & a_Callback); // Lua-acessible
+
/** Calls the callback for the chest at the specified coords; returns false if there's no chest at those coords, true if found */
bool DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback); // Lua-acessible
diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp
index 59a743746..d95ffd6ef 100644
--- a/src/ChunkMap.cpp
+++ b/src/ChunkMap.cpp
@@ -2085,6 +2085,21 @@ bool cChunkMap::ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEnti
+bool cChunkMap::ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback & a_Callback)
+{
+ cCSLock Lock(m_CSLayers);
+ cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ);
+ if ((Chunk == nullptr) || !Chunk->IsValid())
+ {
+ return false;
+ }
+ return Chunk->ForEachBrewingstand(a_Callback);
+}
+
+
+
+
+
bool cChunkMap::ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & a_Callback)
{
cCSLock Lock(m_CSLayers);
@@ -2196,6 +2211,24 @@ bool cChunkMap::DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeacon
+bool cChunkMap::DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback & a_Callback)
+{
+ int ChunkX, ChunkZ;
+ int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
+ cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
+ cCSLock Lock(m_CSLayers);
+ cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ);
+ if ((Chunk == nullptr) || !Chunk->IsValid())
+ {
+ return false;
+ }
+ return Chunk->DoWithBrewingstandAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
+}
+
+
+
+
+
bool cChunkMap::DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback)
{
int ChunkX, ChunkZ;
diff --git a/src/ChunkMap.h b/src/ChunkMap.h
index e9309dbd8..e229d108c 100644
--- a/src/ChunkMap.h
+++ b/src/ChunkMap.h
@@ -20,6 +20,7 @@ class cChunkStay;
class cChunk;
class cPlayer;
class cBeaconEntity;
+class cBrewingstandEntity;
class cChestEntity;
class cDispenserEntity;
class cDropperEntity;
@@ -43,6 +44,7 @@ typedef cChunk * cChunkPtr;
typedef cItemCallback<cEntity> cEntityCallback;
typedef cItemCallback<cBlockEntity> cBlockEntityCallback;
typedef cItemCallback<cBeaconEntity> cBeaconCallback;
+typedef cItemCallback<cBrewingstandEntity> cBrewingstandCallback;
typedef cItemCallback<cChestEntity> cChestCallback;
typedef cItemCallback<cDispenserEntity> cDispenserCallback;
typedef cItemCallback<cDropperEntity> cDropperCallback;
@@ -246,6 +248,10 @@ public:
Returns true if all block entities processed, false if the callback aborted by returning true. */
bool ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityCallback & a_Callback); // Lua-accessible
+ /** Calls the callback for brewingstand in the specified chunk.
+ Returns true if all brewingstands processed, false if the callback aborted by returning true. */
+ bool ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback & a_Callback); // Lua-accessible
+
/** Calls the callback for each chest in the specified chunk.
Returns true if all chests processed, false if the callback aborted by returning true. */
bool ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & a_Callback); // Lua-accessible
@@ -274,6 +280,9 @@ public:
Returns false if there's no beacon at those coords, true if found. */
bool DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback & a_Callback); // Lua-acessible
+ /** Calls the callback for the brewingstand at the specified coords; returns false if there's no brewingstand at those coords, true if found */
+ bool DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback & a_Callback); // Lua-acessible
+
/** Calls the callback for the chest at the specified coords.
Returns false if there's no chest at those coords, true if found. */
bool DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback); // Lua-acessible
diff --git a/src/Root.cpp b/src/Root.cpp
index 57056cc1b..ed9076268 100644
--- a/src/Root.cpp
+++ b/src/Root.cpp
@@ -5,6 +5,7 @@
#include "Server.h"
#include "World.h"
#include "WebAdmin.h"
+#include "BrewingRecipes.h"
#include "FurnaceRecipe.h"
#include "CraftingRecipes.h"
#include "Bindings/PluginManager.h"
@@ -53,6 +54,7 @@ cRoot::cRoot(void) :
m_MonsterConfig(nullptr),
m_CraftingRecipes(nullptr),
m_FurnaceRecipe(nullptr),
+ m_BrewingRecipes(nullptr),
m_WebAdmin(nullptr),
m_PluginManager(nullptr),
m_MojangAPI(nullptr)
@@ -169,6 +171,7 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo)
m_RankManager->Initialize(*m_MojangAPI);
m_CraftingRecipes = new cCraftingRecipes();
m_FurnaceRecipe = new cFurnaceRecipe();
+ m_BrewingRecipes.reset(new cBrewingRecipes());
LOGD("Loading worlds...");
LoadWorlds(*settingsRepo);
@@ -913,8 +916,3 @@ int cRoot::GetFurnaceFuelBurnTime(const cItem & a_Fuel)
cFurnaceRecipe * FR = Get()->GetFurnaceRecipe();
return FR->GetBurnTime(a_Fuel);
}
-
-
-
-
-
diff --git a/src/Root.h b/src/Root.h
index 772d858d9..142b323a5 100644
--- a/src/Root.h
+++ b/src/Root.h
@@ -16,6 +16,7 @@
// fwd:
class cThread;
class cMonsterConfig;
+class cBrewingRecipes;
class cCraftingRecipes;
class cFurnaceRecipe;
class cWebAdmin;
@@ -88,6 +89,7 @@ public:
cCraftingRecipes * GetCraftingRecipes(void) { return m_CraftingRecipes; } // tolua_export
cFurnaceRecipe * GetFurnaceRecipe (void) { return m_FurnaceRecipe; } // Exported in ManualBindings.cpp with quite a different signature
+ cBrewingRecipes * GetBrewingRecipes (void) { return m_BrewingRecipes.get(); } // Exported in ManualBindings.cpp
/** Returns the number of ticks for how long the item would fuel a furnace. Returns zero if not a fuel */
static int GetFurnaceFuelBurnTime(const cItem & a_Fuel); // tolua_export
@@ -208,6 +210,7 @@ private:
cCraftingRecipes * m_CraftingRecipes;
cFurnaceRecipe * m_FurnaceRecipe;
+ std::unique_ptr<cBrewingRecipes> m_BrewingRecipes;
cWebAdmin * m_WebAdmin;
cPluginManager * m_PluginManager;
cAuthenticator m_Authenticator;
diff --git a/src/UI/BrewingstandWindow.cpp b/src/UI/BrewingstandWindow.cpp
new file mode 100644
index 000000000..2f1f3c97d
--- /dev/null
+++ b/src/UI/BrewingstandWindow.cpp
@@ -0,0 +1,70 @@
+
+// BrewingstandWindow.cpp
+
+// Representing the UI window for the brewing stand block
+
+#include "Globals.h"
+#include "BrewingstandWindow.h"
+#include "SlotArea.h"
+#include "../BrewingRecipes.h"
+#include "../Root.h"
+
+
+
+
+
+cBrewingstandWindow::cBrewingstandWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandEntity * a_Brewingstand) :
+ cWindow(wtBrewery, "Brewingstand")
+{
+ m_SlotAreas.push_back(new cSlotAreaBrewingstand(a_Brewingstand, *this));
+ m_SlotAreas.push_back(new cSlotAreaInventory(*this));
+ m_SlotAreas.push_back(new cSlotAreaHotBar(*this));
+}
+
+
+
+
+
+void cBrewingstandWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply)
+{
+ cSlotAreas AreasInOrder;
+
+ if (a_ClickedArea == m_SlotAreas[0])
+ {
+ // Brewing stand Area
+ if ((a_Slot >= 0) && (a_Slot < 3))
+ {
+ // Bottle slots
+ AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */
+ AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */
+ super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true);
+ }
+ else
+ {
+ // Ingredient slot
+ AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */
+ AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */
+ super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false);
+ }
+ }
+ else
+ {
+ cBrewingRecipes * BR = cRoot::Get()->GetBrewingRecipes();
+ if ((BR->IsBottle(a_ItemStack)) || (BR->IsIngredient(a_ItemStack)))
+ {
+ AreasInOrder.push_back(m_SlotAreas[0]); /* brewing stand Area */
+ }
+ else if (a_ClickedArea == m_SlotAreas[1])
+ {
+ // Inventory Area
+ AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */
+ }
+ else
+ {
+ // Hotbar Area
+ AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */
+ }
+
+ super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false);
+ }
+}
diff --git a/src/UI/BrewingstandWindow.h b/src/UI/BrewingstandWindow.h
new file mode 100644
index 000000000..e55752187
--- /dev/null
+++ b/src/UI/BrewingstandWindow.h
@@ -0,0 +1,31 @@
+
+// BrewingstandWindow.h
+
+// Representing the UI window for the brewing stand block
+
+
+
+
+
+#pragma once
+
+#include "Window.h"
+
+
+
+
+
+class cBrewingstandWindow :
+ public cWindow
+{
+ typedef cWindow super;
+
+public:
+ cBrewingstandWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandEntity * a_Brewingstand);
+
+ virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override;
+};
+
+
+
+
diff --git a/src/UI/CMakeLists.txt b/src/UI/CMakeLists.txt
index b907919c8..ad3c00f5f 100644
--- a/src/UI/CMakeLists.txt
+++ b/src/UI/CMakeLists.txt
@@ -9,6 +9,7 @@ SET (SRCS
Window.cpp
AnvilWindow.cpp
BeaconWindow.cpp
+ BrewingstandWindow.cpp
ChestWindow.cpp
CraftingWindow.cpp
DropSpenserWindow.cpp
@@ -23,6 +24,7 @@ SET (HDRS
Window.h
AnvilWindow.h
BeaconWindow.h
+ BrewingstandWindow.h
ChestWindow.h
CraftingWindow.h
DropSpenserWindow.h
diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp
index 478c004fb..2c0dfbe40 100644
--- a/src/UI/SlotArea.cpp
+++ b/src/UI/SlotArea.cpp
@@ -7,6 +7,7 @@
#include "SlotArea.h"
#include "../Entities/Player.h"
#include "../BlockEntities/BeaconEntity.h"
+#include "../BlockEntities/BrewingstandEntity.h"
#include "../BlockEntities/ChestEntity.h"
#include "../BlockEntities/DropSpenserEntity.h"
#include "../BlockEntities/EnderChestEntity.h"
@@ -1935,6 +1936,242 @@ void cSlotAreaFurnace::HandleSmeltItem(const cItem & a_Result, cPlayer & a_Playe
////////////////////////////////////////////////////////////////////////////////
+// cSlotAreaBrewingstand:
+cSlotAreaBrewingstand::cSlotAreaBrewingstand(cBrewingstandEntity * a_Brewingstand, cWindow & a_ParentWindow) :
+ cSlotArea(4, 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 ((a_SlotNum >= 0) && (a_SlotNum < 3))
+ {
+ 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:
+ {
+ HandleBrewedItem(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;
+ HandleBrewedItem(a_Player);
+ return;
+ }
+ default:
+ {
+ break;
+ }
+ }
+
+ cItem & DraggingItem = a_Player.GetDraggingItem();
+ if (!DraggingItem.IsEmpty())
+ {
+ super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
+ return;
+ }
+ else
+ {
+ switch (a_ClickAction)
+ {
+ case caDblClick:
+ {
+ DblClicked(a_Player, a_SlotNum);
+ return;
+ }
+ case caLeftClick:
+ {
+ DraggingItem = Slot;
+ HandleBrewedItem(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();
+ }
+ HandleBrewedItem(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 cSlotAreaBrewingstand::HandleBrewedItem(cPlayer & a_Player)
+{
+ 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) :
@@ -2342,3 +2579,4 @@ cItem * cSlotAreaTemporary::GetPlayerSlots(cPlayer & a_Player)
+
diff --git a/src/UI/SlotArea.h b/src/UI/SlotArea.h
index b150c47a1..0ff36ce50 100644
--- a/src/UI/SlotArea.h
+++ b/src/UI/SlotArea.h
@@ -16,6 +16,7 @@
class cWindow;
class cPlayer;
class cBeaconEntity;
+class cBrewingstandEntity;
class cChestEntity;
class cEnderChestEntity;
class cFurnaceEntity;
@@ -456,6 +457,35 @@ protected:
+class cSlotAreaBrewingstand :
+ public cSlotArea,
+ public cItemGrid::cListener
+{
+ typedef cSlotArea super;
+
+public:
+ cSlotAreaBrewingstand(cBrewingstandEntity * a_Brewingstand, cWindow & a_ParentWindow);
+
+ virtual ~cSlotAreaBrewingstand();
+
+ virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override;
+ virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) override;
+ virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const override;
+ virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override;
+protected:
+ cBrewingstandEntity * m_Brewingstand;
+
+ // cItemGrid::cListener overrides:
+ virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) override;
+
+ /** Called after an item has been brewed to handle statistics etc. */
+ void HandleBrewedItem(cPlayer & a_Player);
+} ;
+
+
+
+
+
class cSlotAreaMinecartWithChest :
public cSlotArea
{
diff --git a/src/UI/Window.h b/src/UI/Window.h
index cb8f40767..76d22a12c 100644
--- a/src/UI/Window.h
+++ b/src/UI/Window.h
@@ -18,6 +18,7 @@
class cPlayer;
class cWindowOwner;
class cClientHandle;
+class cBrewingstandEntity;
class cChestEntity;
class cEnderChestEntity;
class cFurnaceEntity;
diff --git a/src/World.cpp b/src/World.cpp
index de0dc6ea5..bd06af1b7 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -1151,6 +1151,15 @@ bool cWorld::ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityC
+bool cWorld::ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback & a_Callback)
+{
+ return m_ChunkMap->ForEachBrewingstandInChunk(a_ChunkX, a_ChunkZ, a_Callback);
+}
+
+
+
+
+
bool cWorld::ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & a_Callback)
{
return m_ChunkMap->ForEachChestInChunk(a_ChunkX, a_ChunkZ, a_Callback);
@@ -1260,6 +1269,15 @@ bool cWorld::DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCal
+bool cWorld::DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback & a_Callback)
+{
+ return m_ChunkMap->DoWithBrewingstandAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
+}
+
+
+
+
+
bool cWorld::DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback)
{
return m_ChunkMap->DoWithChestAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
diff --git a/src/World.h b/src/World.h
index 8529ffb4d..30ac52763 100644
--- a/src/World.h
+++ b/src/World.h
@@ -46,6 +46,7 @@ class cBlockEntity;
class cWorldGenerator; // The generator that actually generates the chunks for a single world
class cChunkGenerator; // The thread responsible for generating chunks
class cBeaconEntity;
+class cBrewingstandEntity;
class cChestEntity;
class cDispenserEntity;
class cFlowerPotEntity;
@@ -66,6 +67,7 @@ typedef std::vector<cSetChunkDataPtr> cSetChunkDataPtrs;
typedef cItemCallback<cPlayer> cPlayerListCallback;
typedef cItemCallback<cEntity> cEntityCallback;
typedef cItemCallback<cBeaconEntity> cBeaconCallback;
+typedef cItemCallback<cBrewingstandEntity> cBrewingstandCallback;
typedef cItemCallback<cChestEntity> cChestCallback;
typedef cItemCallback<cDispenserEntity> cDispenserCallback;
typedef cItemCallback<cFurnaceEntity> cFurnaceCallback;
@@ -496,6 +498,9 @@ public:
/** Calls the callback for each block entity in the specified chunk; returns true if all block entities processed, false if the callback aborted by returning true */
bool ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityCallback & a_Callback); // Exported in ManualBindings.cpp
+ /** Calls the callback for each brewingstand in the specified chunk; returns true if all brewingstands processed, false if the callback aborted by returning true */
+ bool ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback & a_Callback); // Exported in ManualBindings.cpp
+
/** Calls the callback for each chest in the specified chunk; returns true if all chests processed, false if the callback aborted by returning true */
bool ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & a_Callback); // Exported in ManualBindings.cpp
@@ -532,6 +537,9 @@ public:
/** Calls the callback for the beacon at the specified coords; returns false if there's no beacon at those coords, true if found */
bool DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback & a_Callback); // Exported in ManualBindings.cpp
+ /** Calls the callback for the brewingstand at the specified coords; returns false if there's no brewingstand at those coords, true if found */
+ bool DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback & a_Callback); // Lua-acessible
+
/** Calls the callback for the chest at the specified coords; returns false if there's no chest at those coords, true if found */
bool DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback); // Exported in ManualBindings.cpp
diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp
index e2ba416c6..607a9c182 100644
--- a/src/WorldStorage/NBTChunkSerializer.cpp
+++ b/src/WorldStorage/NBTChunkSerializer.cpp
@@ -11,6 +11,7 @@
#include "FastNBT.h"
#include "../BlockEntities/BeaconEntity.h"
+#include "../BlockEntities/BrewingstandEntity.h"
#include "../BlockEntities/ChestEntity.h"
#include "../BlockEntities/CommandBlockEntity.h"
#include "../BlockEntities/DispenserEntity.h"
@@ -196,6 +197,21 @@ void cNBTChunkSerializer::AddBeaconEntity(cBeaconEntity * a_Entity)
+void cNBTChunkSerializer::AddBrewingstandEntity(cBrewingstandEntity * a_Brewingstand)
+{
+ m_Writer.BeginCompound("");
+ AddBasicTileEntity(a_Brewingstand, "Brewingstand");
+ m_Writer.BeginList("Items", TAG_Compound);
+ AddItemGrid(a_Brewingstand->GetContents());
+ m_Writer.EndList();
+ m_Writer.AddShort("BrewTime", a_Brewingstand->GetTimeBrewed());
+ m_Writer.EndCompound();
+}
+
+
+
+
+
void cNBTChunkSerializer::AddChestEntity(cChestEntity * a_Entity, BLOCKTYPE a_ChestType)
{
m_Writer.BeginCompound("");
@@ -938,6 +954,7 @@ void cNBTChunkSerializer::BlockEntity(cBlockEntity * a_Entity)
switch (a_Entity->GetBlockType())
{
case E_BLOCK_BEACON: AddBeaconEntity (reinterpret_cast<cBeaconEntity *> (a_Entity)); break;
+ case E_BLOCK_BREWING_STAND: AddBrewingstandEntity(reinterpret_cast<cBrewingstandEntity *>(a_Entity)); break;
case E_BLOCK_CHEST: AddChestEntity (reinterpret_cast<cChestEntity *> (a_Entity), a_Entity->GetBlockType()); break;
case E_BLOCK_COMMAND_BLOCK: AddCommandBlockEntity(reinterpret_cast<cCommandBlockEntity *>(a_Entity)); break;
case E_BLOCK_DISPENSER: AddDispenserEntity (reinterpret_cast<cDispenserEntity *> (a_Entity)); break;
diff --git a/src/WorldStorage/NBTChunkSerializer.h b/src/WorldStorage/NBTChunkSerializer.h
index 956738911..9cdfb1a76 100644
--- a/src/WorldStorage/NBTChunkSerializer.h
+++ b/src/WorldStorage/NBTChunkSerializer.h
@@ -21,6 +21,7 @@ class cEntity;
class cBlockEntity;
class cBoat;
class cBeaconEntity;
+class cBrewingstandEntity;
class cChestEntity;
class cCommandBlockEntity;
class cDispenserEntity;
@@ -97,6 +98,7 @@ protected:
// Block entities:
void AddBasicTileEntity (cBlockEntity * a_Entity, const char * a_EntityTypeID);
void AddBeaconEntity (cBeaconEntity * a_Entity);
+ void AddBrewingstandEntity(cBrewingstandEntity * a_Brewingstand);
void AddChestEntity (cChestEntity * a_Entity, BLOCKTYPE a_ChestType);
void AddDispenserEntity (cDispenserEntity * a_Entity);
void AddDropperEntity (cDropperEntity * a_Entity);
diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp
index f0c990037..38ca1cdd9 100755
--- a/src/WorldStorage/WSSAnvil.cpp
+++ b/src/WorldStorage/WSSAnvil.cpp
@@ -18,6 +18,7 @@
#include "../Root.h"
#include "../BlockEntities/BeaconEntity.h"
+#include "../BlockEntities/BrewingstandEntity.h"
#include "../BlockEntities/ChestEntity.h"
#include "../BlockEntities/CommandBlockEntity.h"
#include "../BlockEntities/DispenserEntity.h"
@@ -689,6 +690,7 @@ cBlockEntity * cWSSAnvil::LoadBlockEntityFromNBT(const cParsedNBT & a_NBT, int a
{
// Specific entity loaders:
case E_BLOCK_BEACON: return LoadBeaconFromNBT (a_NBT, a_Tag, a_BlockX, a_BlockY, a_BlockZ);
+ case E_BLOCK_BREWING_STAND: return LoadBrewingstandFromNBT(a_NBT, a_Tag, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BREWING_STAND, a_BlockMeta);
case E_BLOCK_CHEST: return LoadChestFromNBT (a_NBT, a_Tag, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_CHEST);
case E_BLOCK_COMMAND_BLOCK: return LoadCommandBlockFromNBT(a_NBT, a_Tag, a_BlockX, a_BlockY, a_BlockZ);
case E_BLOCK_DISPENSER: return LoadDispenserFromNBT (a_NBT, a_Tag, a_BlockX, a_BlockY, a_BlockZ);
@@ -926,6 +928,55 @@ cBlockEntity * cWSSAnvil::LoadBeaconFromNBT(const cParsedNBT & a_NBT, int a_TagI
+cBlockEntity * cWSSAnvil::LoadBrewingstandFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
+{
+ // Check if the data has a proper type:
+ if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Brewingstand"))
+ {
+ return nullptr;
+ }
+
+ int Items = a_NBT.FindChildByName(a_TagIdx, "Items");
+ if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List))
+ {
+ return nullptr; // Make it an empty brewingstand - the chunk loader will provide an empty cBrewingstandEntity for this
+ }
+
+ std::unique_ptr<cBrewingstandEntity> Brewingstand(new cBrewingstandEntity(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, m_World));
+
+ // Load slots:
+ for (int Child = a_NBT.GetFirstChild(Items); Child != -1; Child = a_NBT.GetNextSibling(Child))
+ {
+ int Slot = a_NBT.FindChildByName(Child, "Slot");
+ if ((Slot < 0) || (a_NBT.GetType(Slot) != TAG_Byte))
+ {
+ continue;
+ }
+ cItem Item;
+ if (LoadItemFromNBT(Item, a_NBT, Child))
+ {
+ Brewingstand->SetSlot(a_NBT.GetByte(Slot), Item);
+ }
+ } // for itr - ItemDefs[]
+
+ // Load brewing time:
+ int BrewTime = a_NBT.FindChildByName(a_TagIdx, "BrewTime");
+ if (BrewTime >= 0)
+ {
+ Int16 tb = a_NBT.GetShort(BrewTime);
+ Brewingstand->setTimeBrewed(tb);
+ }
+
+ // Restart brewing:
+ Brewingstand->GetRecipes();
+ Brewingstand->ContinueBrewing();
+ return Brewingstand.release();
+}
+
+
+
+
+
cBlockEntity * cWSSAnvil::LoadChestFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_ChestBlockType)
{
// Check if the data has a proper type:
diff --git a/src/WorldStorage/WSSAnvil.h b/src/WorldStorage/WSSAnvil.h
index 6c15877ff..03fa22457 100755
--- a/src/WorldStorage/WSSAnvil.h
+++ b/src/WorldStorage/WSSAnvil.h
@@ -149,6 +149,7 @@ protected:
bool CheckBlockEntityType(const cParsedNBT & a_NBT, int a_TagIdx, const char * a_ExpectedType);
cBlockEntity * LoadBeaconFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, int a_BlockX, int a_BlockY, int a_BlockZ);
+ cBlockEntity * LoadBrewingstandFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
cBlockEntity * LoadChestFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_ChestBlockType);
cBlockEntity * LoadCommandBlockFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, int a_BlockX, int a_BlockY, int a_BlockZ);
cBlockEntity * LoadDispenserFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, int a_BlockX, int a_BlockY, int a_BlockZ);