Parcourir la source

add wooden buckets to move or drink fluids

Kolja Strohm il y a 9 mois
Parent
commit
f429912cd5

+ 10 - 12
FactoryCraft/BlockType.cpp

@@ -99,20 +99,18 @@ void BlockType::createSuperBlock(Block* zBlock, Item* zItem) const
     if (zItem)
     {
         BasicBlockItem* item = dynamic_cast<BasicBlockItem*>(zItem);
-        if (!item)
+        if (item)
         {
-            throw "BlockType::createSuperBlock was called with an item witch "
-                  "was not an instance of BasicBlockItem";
+            zBlock->transparent = item->transparent;
+            zBlock->passable = item->passable;
+            zBlock->hardness = item->hardness;
+            zBlock->speedModifier = item->speedModifier;
+            zBlock->zTool
+                = item->toolId >= 0
+                    ? StaticRegistry<ItemType>::INSTANCE.zElement(item->toolId)
+                    : 0;
+            zBlock->interactable = item->interactable;
         }
-        zBlock->transparent = item->transparent;
-        zBlock->passable = item->passable;
-        zBlock->hardness = item->hardness;
-        zBlock->speedModifier = item->speedModifier;
-        zBlock->zTool
-            = item->toolId >= 0
-                ? StaticRegistry<ItemType>::INSTANCE.zElement(item->toolId)
-                : 0;
-        zBlock->interactable = item->interactable;
     }
     zBlock->mapColor = initialMapColor;
 }

+ 77 - 26
FactoryCraft/Entity.cpp

@@ -55,7 +55,7 @@ bool ActionTarget::useItemSkillOnTarget(
     return 0;
 }
 
-void ActionTarget::interactItemSkillOnTarget(
+bool ActionTarget::interactItemSkillOnTarget(
     Entity* zActor, ItemSkill* zItemSkill, Item* zUsedItem)
 {
     if (zItemSkill)
@@ -63,13 +63,13 @@ void ActionTarget::interactItemSkillOnTarget(
         if (entityId >= 0)
         {
             Entity* target = Game::INSTANCE->zEntity(entityId);
-            if (target) zItemSkill->interact(zActor, zUsedItem, target);
+            if (target) return zItemSkill->interact(zActor, zUsedItem, target);
         }
         else
         {
             Block* block = Game::INSTANCE->zRealBlockInstance(
                 blockPos, zActor->getDimensionId());
-            if (block) zItemSkill->interact(zActor, zUsedItem, block);
+            if (block) return zItemSkill->interact(zActor, zUsedItem, block);
         }
     }
     else
@@ -78,15 +78,16 @@ void ActionTarget::interactItemSkillOnTarget(
         {
             Block* block = Game::INSTANCE->zRealBlockInstance(
                 blockPos, zActor->getDimensionId());
-            if (block) block->interact(zUsedItem, zActor);
+            if (block) return block->interact(zUsedItem, zActor);
         }
         else
         {
             Block* block = Game::INSTANCE->zRealBlockInstance(
                 blockPos, zActor->getDimensionId());
-            if (block) block->interact(zUsedItem, zActor);
+            if (block) return block->interact(zUsedItem, zActor);
         }
     }
+    return 0;
 }
 
 bool ActionTarget::placeBlock(Entity* zActor, Item* zItem)
@@ -274,6 +275,53 @@ bool Entity::useItem(int typeId, ItemStack* zStack, bool left)
     }
     else
     {
+        if (zStack && zStack->zItem() && zStack->zItem()->isPlaceable()
+            && zStack->getSize() > 0)
+        { // place item
+            cs.lock();
+            if (target)
+            {
+                if (placeBlockCooldown <= 0)
+                {
+                    Item* item = zStack->extractFromStack();
+                    bool result = target->placeBlock(this, item);
+                    if (item->getHp() > 0)
+                    {
+                        if (!zStack->addToStack(
+                                dynamic_cast<Item*>(item->getThis())))
+                        {
+                            ItemStack* newStack = new ItemStack(item, 1);
+                            addItems(newStack, NO_DIRECTION, 0);
+                            if (newStack->getSize())
+                            {
+                                Game::INSTANCE->spawnItem(location,
+                                    dimensionId, newStack);
+                            }
+                        }
+                        else
+                        {
+                            item->release();
+                        }
+                    }
+                    else
+                    {
+                        item->release();
+                    }
+                    if (result)
+                    {
+                        placeBlockCooldown = 15;
+                    }
+                    cs.unlock();
+                    return result;
+                }
+                else
+                {
+                	cs.unlock();
+					return 0;
+                }
+            }
+            cs.unlock();
+        }
         if (zStack && zStack->zItem() && zStack->zItem()->isEatable()
             && zStack->getSize() > 0)
         { // eat item
@@ -292,32 +340,35 @@ bool Entity::useItem(int typeId, ItemStack* zStack, bool left)
                 }
             }
         }
-        else if (zStack && zStack->zItem() && zStack->zItem()->isPlaceable()
-                 && zStack->getSize() > 0)
-        { // place item
-            if (placeBlockCooldown <= 0)
+        if (!zStack || !zStack->zItem() || zStack->zItem()->isUsable())
+        {
+            cs.lock();
+            if (target)
             {
-                cs.lock();
-                if (target)
+                ItemSkill* selected = zSkill(typeId);
+                if (!selected)
                 {
-                    Item* item = zStack->extractFromStack();
-                    if (!target->placeBlock(this, item))
-                    {
-                        if (!zStack->addToStack(item))
-                        {
-                            Game::INSTANCE->spawnItem(
-                                location, dimensionId, new ItemStack(item, 1));
-                        }
-                        cs.unlock();
-                        return 0;
-                    }
-                    item->release();
-                    placeBlockCooldown = 15;
-                    cs.unlock();
-                    return 1;
+                    selected
+                        = StaticRegistry<ItemType>::INSTANCE.zElement(typeId)
+                              ->createDefaultItemSkill();
+                    if (selected) skills.add(selected);
                 }
+                if (!selected)
+                {
+                    selected = zSkill(ItemTypeEnum::PLAYER_HAND);
+                }
+                bool result = target->interactItemSkillOnTarget(this,
+                    selected,
+                    !zStack || zStack->getSize() > 1 ? 0
+                                                     : (Item*)zStack->zItem());
                 cs.unlock();
+                return result;
             }
+            cs.unlock();
+        }
+        else
+        {
+            useItem(ItemTypeEnum::PLAYER_HAND, 0, left);
         }
     }
     return 0;

+ 1 - 1
FactoryCraft/Entity.h

@@ -31,7 +31,7 @@ public:
 
     bool useItemSkillOnTarget(
         Entity* zActor, ItemSkill* zItemSkill, Item* zUsedItem);
-    void interactItemSkillOnTarget(
+    bool interactItemSkillOnTarget(
         Entity* zActor, ItemSkill* zItemSkill, Item* zUsedItem);
     bool placeBlock(Entity* zActor, Item* zItem);
 

+ 2 - 0
FactoryCraft/FactoryCraft.vcxproj

@@ -100,6 +100,7 @@
     <ClInclude Include="BlockInstanceGeneratorRule.h" />
     <ClInclude Include="FactorizeNoise.h" />
     <ClInclude Include="FlattenNoise.h" />
+    <ClInclude Include="FluidContainer.h" />
     <ClInclude Include="GeneratorRule.h" />
     <ClInclude Include="BlockTypeGeneratorRule.h" />
     <ClInclude Include="Chest.h" />
@@ -232,6 +233,7 @@
     <ClCompile Include="FastNoiseWrapper.cpp" />
     <ClCompile Include="FlattenNoise.cpp" />
     <ClCompile Include="FluidBlock.cpp" />
+    <ClCompile Include="FluidContainer.cpp" />
     <ClCompile Include="Game.cpp" />
     <ClCompile Include="GeneratedStructure.cpp" />
     <ClCompile Include="GeneratorTemplate.cpp" />

+ 9 - 0
FactoryCraft/FactoryCraft.vcxproj.filters

@@ -103,6 +103,9 @@
     <Filter Include="world\generator\dimensions">
       <UniqueIdentifier>{ad05d3d5-d516-4582-ba50-74f9ee112ee1}</UniqueIdentifier>
     </Filter>
+    <Filter Include="inventory\items\fluidContainer">
+      <UniqueIdentifier>{59a5d9f2-a3fb-45a5-92c8-c641f2891b01}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Chunk.h">
@@ -408,6 +411,9 @@
     <ClInclude Include="OverworldDimensionGenerator.h">
       <Filter>world\generator\dimensions</Filter>
     </ClInclude>
+    <ClInclude Include="FluidContainer.h">
+      <Filter>inventory\items\fluidContainer</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Server.cpp">
@@ -701,5 +707,8 @@
     <ClCompile Include="OverworldDimensionGenerator.cpp">
       <Filter>world\generator\dimensions</Filter>
     </ClCompile>
+    <ClCompile Include="FluidContainer.cpp">
+      <Filter>inventory\items\fluidContainer</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 60 - 8
FactoryCraft/FluidBlock.cpp

@@ -1,5 +1,6 @@
 #include "FluidBlock.h"
 
+#include "FluidContainer.h"
 #include "Game.h"
 
 FluidBlock::FluidBlock(int typeId,
@@ -73,20 +74,34 @@ void FluidBlock::doFlow()
 {
     bool doesFlowSidewards = 1;
     if (((zNeighbours[getDirectionIndex(Direction::BOTTOM)]
-                && zNeighbours[getDirectionIndex(Direction::BOTTOM)]
-                           ->zBlockType()
-                       == zBlockType()
-            || neighbourTypes[getDirectionIndex(Direction::BOTTOM)]
-                   == BlockTypeEnum::AIR)
-        && distanceToSource) || distanceToSource >= maxFlowDistance)
+                 && zNeighbours[getDirectionIndex(Direction::BOTTOM)]
+                            ->zBlockType()
+                        == zBlockType()
+             || neighbourTypes[getDirectionIndex(Direction::BOTTOM)]
+                    == BlockTypeEnum::AIR)
+            && distanceToSource)
+        || distanceToSource >= maxFlowDistance)
     {
         doesFlowSidewards = 0;
     }
     bool changed = false;
+    int minNeighborDistance = maxFlowDistance;
     for (int i = 0; i < 6; i++)
     {
         Direction dir = getDirectionFromIndex(i);
-        if (dir & Direction::TOP) continue;
+        if (dir & Direction::TOP)
+        {
+            if (zNeighbours[i] && zNeighbours[i]->zBlockType() == zBlockType())
+            {
+                FluidBlock* neighbour
+                    = dynamic_cast<FluidBlock*>(zNeighbours[i]);
+                minNeighborDistance = 0;
+                distanceToSource = neighbour->distanceToSource + 1;
+                flowOptions = (char)getOppositeDirection(dir);
+                changed = true;
+            }
+            continue;
+        }
         if (neighbourTypes[i] == BlockTypeEnum::AIR)
         {
             if (dir & Direction::BOTTOM || doesFlowSidewards)
@@ -138,9 +153,27 @@ void FluidBlock::doFlow()
                     flowOptions |= (char)getOppositeDirection(dir);
                     changed |= tmp != flowOptions;
                 }
+                if (neighbour->distanceToSource < minNeighborDistance)
+                {
+                    minNeighborDistance = neighbour->distanceToSource;
+                }
             }
         }
     }
+    if (distanceToSource)
+    {
+        if (minNeighborDistance + 1 > distanceToSource)
+        {
+            distanceToSource = minNeighborDistance + 1;
+            flowOptions = 0; // reavaluated next tick
+            changed = true;
+        }
+    }
+    if (distanceToSource > maxFlowDistance)
+    {
+        distanceToSource = maxFlowDistance;
+        Game::INSTANCE->doLater([this]() { setHP(0.f); });
+    }
     if (changed)
     {
         broadcastModelInfoChange();
@@ -162,6 +195,12 @@ void FluidBlock::doFlow()
     }
 }
 
+bool FluidBlock::isInteractable(const Item* zItem) const
+{
+    return distanceToSource == 0
+        && dynamic_cast<const FluidContainerItem*>(zItem);
+}
+
 void FluidBlock::filterPassingLight(unsigned char rgb[3]) const
 {
     rgb[0] = (unsigned char)(rgb[0] * lightWeights.x);
@@ -240,4 +279,17 @@ int FluidBlockType::getTicktsToFlow() const
 char FluidBlockType::getFlowDistance() const
 {
     return flowDistance;
-}
+}
+
+std::function<bool(FluidContainerItem*, Entity*)>
+FluidBlockType::getFoodEffect() const
+{
+    return foodEffect;
+}
+
+FluidBlockType* FluidBlockType::setFoodEffect(
+    std::function<bool(FluidContainerItem*, Entity*)> foodEffect)
+{
+    this->foodEffect = foodEffect;
+    return this;
+}

+ 7 - 0
FactoryCraft/FluidBlock.h

@@ -4,6 +4,7 @@
 #include "TickQueue.h"
 
 class FluidBlockType;
+class FluidContainerItem;
 
 class FluidBlock : public Block
 {
@@ -31,6 +32,7 @@ public:
     virtual void setNeighbourType(Direction dir, int type) override;
     virtual void sendModelInfo(NetworkMessage* zMessage) override;
 
+    virtual bool isInteractable(const Item* zItem) const override;
     virtual void filterPassingLight(unsigned char rgb[3]) const override;
 
     char getDistanceToSource() const;
@@ -45,6 +47,7 @@ private:
     Framework::Vec3<float> lightWeights;
     int ticktsToFlow;
     char flowDistance;
+    std::function<bool(FluidContainerItem*, Entity*)> foodEffect;
 
 protected:
     virtual void loadSuperBlock(Block* zBlock,
@@ -68,4 +71,8 @@ public:
     bool isFluid() const override;
     int getTicktsToFlow() const;
     char getFlowDistance() const override;
+    std::function<bool(FluidContainerItem*, Entity*)> getFoodEffect() const;
+
+    FluidBlockType* setFoodEffect(
+        std::function<bool(FluidContainerItem*, Entity*)> foodEffect);
 };

+ 367 - 0
FactoryCraft/FluidContainer.cpp

@@ -0,0 +1,367 @@
+#include "FluidContainer.h"
+
+#include <TextFeld.h>
+
+#include "FluidBlock.h"
+#include "Game.h"
+
+FluidContainerItem::FluidContainerItem(int itemTypeId, const char* name)
+    : Item(itemTypeId, name),
+      fluidTypeId(0),
+      fluidAmount(0)
+{
+    placeable = 1;
+    usable = 1;
+    eatable = 1;
+}
+
+const BlockType* FluidContainerItem::zPlacedBlockType() const
+{
+    return fluidTypeId && fluidAmount >= 1000 ? StaticRegistry<BlockType>::INSTANCE.zElement(fluidTypeId) : 0;
+}
+
+bool FluidContainerItem::canBeStackedWith(const Item* zItem) const
+{
+    const FluidContainerItem* other
+        = dynamic_cast<const FluidContainerItem*>(zItem);
+    if (!other) return false;
+    return Item::canBeStackedWith(zItem) && other->fluidTypeId == fluidTypeId
+        && other->fluidAmount == fluidAmount;
+}
+
+bool FluidContainerItem::canBePlacedAt(
+    const int dimensionId, Framework::Vec3<int> worldPos) const
+{
+    if (fluidAmount >= 1000)
+    {
+        Dimension* dim = Game::INSTANCE->zDimension(dimensionId);
+        if (dim)
+        {
+            const Block* block = dim->zBlockOrDefault(worldPos);
+            if (block)
+            {
+                if (block->zBlockType()->getId() == BlockTypeEnum::AIR)
+                    return true;
+                if (block->zBlockType()->getId() == fluidTypeId)
+                {
+                    const FluidBlock* fluidBlock
+                        = dynamic_cast<const FluidBlock*>(block);
+                    return fluidBlock && fluidBlock->getDistanceToSource() > 0;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+void FluidContainerItem::onPlaced()
+{
+    setAmount(fluidAmount - 1000);
+}
+
+Framework::Text FluidContainerItem::getTooltipUIML() const
+{
+    Framework::Text uiml = "<tip><text width=\"auto\" height=\"auto\">";
+    uiml.append() << getName();
+    if (fluidTypeId != 0)
+    {
+        uiml.append() << "\nFluid: "
+                      << StaticRegistry<BlockType>::INSTANCE
+                             .zElement(fluidTypeId)
+                             ->getName()
+                      << "\nAmount: " << fluidAmount << " L";
+    }
+    else
+    {
+        uiml.append() << "\nEmpty";
+    }
+    uiml.append() << "</text></tip>";
+    return uiml;
+}
+
+bool FluidContainerItem::applyFoodEffects(Entity* zTarget)
+{
+    if (fluidTypeId)
+    {
+        const FluidBlockType* fluidType = dynamic_cast<const FluidBlockType*>(
+            StaticRegistry<BlockType>::INSTANCE.zElement(fluidTypeId));
+        if (fluidType && fluidType->getFoodEffect())
+        {
+            return fluidType->getFoodEffect()(this, zTarget);
+        }
+    }
+    return false;
+}
+
+bool FluidContainerItem::canApplyFoodEffectsFully(Entity* zTarget) const
+{
+    return false;
+}
+
+int FluidContainerItem::getAmount() const
+{
+    return fluidAmount;
+}
+
+void FluidContainerItem::setAmount(int amount)
+{
+    fluidAmount = amount;
+    if (!fluidAmount)
+    {
+        fluidTypeId = 0;
+    }
+}
+
+int FluidContainerItem::getFluidTypeId() const
+{
+    return fluidTypeId;
+}
+
+void FluidContainerItem::setFluidTypeId(int fluidTypeId)
+{
+    this->fluidTypeId = fluidTypeId;
+    if (!fluidTypeId)
+    {
+        fluidAmount = 0;
+    }
+}
+
+FluidContainerItemSkill::FluidContainerItemSkill(int itemTypeId)
+    : ItemSkill(itemTypeId),
+      level(1),
+      xp(0.f),
+      maxXP(10.f)
+{}
+
+bool FluidContainerItemSkill::use(
+    Entity* zActor, Item* zUsedItem, Block* zTarget)
+{
+    FluidContainerItem* usedItem = dynamic_cast<FluidContainerItem*>(zUsedItem);
+    if (usedItem)
+    {
+        if (zTarget->zBlockType()->isFluid() && zTarget->getHP() > 0)
+        {
+            FluidBlock* fluidBlock = dynamic_cast<FluidBlock*>(zTarget);
+            if (fluidBlock)
+            {
+                if (!usedItem->getFluidTypeId()
+                    || usedItem->getFluidTypeId()
+                           == fluidBlock->zBlockType()->getId())
+                {
+                    const FluidContainerItemType* usedItemType
+                        = dynamic_cast<const FluidContainerItemType*>(
+                            usedItem->zItemType());
+                    if (usedItemType)
+                    {
+                        if (usedItem->getAmount() + 1000
+                            <= usedItemType->getMaxFluidAmount())
+                        {
+                            usedItem->setFluidTypeId(
+                                fluidBlock->zBlockType()->getId());
+                            usedItem->setAmount(usedItem->getAmount() + 1000);
+                            zTarget->setHP(0);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return false;
+}
+
+bool FluidContainerItemSkill::use(
+    Entity* zActor, Item* zUsedItem, Entity* zTarget)
+{
+    // TODO: get milk from cows and something else from other mobs
+    return false;
+}
+
+FluidContainerItemSkillLevelUpRule::FluidContainerItemSkillLevelUpRule()
+    : ItemSkillLevelUpRule()
+{}
+
+void FluidContainerItemSkillLevelUpRule::applyOn(ItemSkill* zSkill)
+{
+    FluidContainerItemSkill* skill
+        = dynamic_cast<FluidContainerItemSkill*>(zSkill);
+    if (skill->xp >= skill->maxXP)
+    {
+        skill->level++;
+        skill->xp = 0;
+        skill->maxXP = skill->maxXP * 2;
+    }
+}
+
+void FluidContainerItemType::loadSuperItem(
+    Item* zItem, Framework::StreamReader* zReader) const
+{
+    ItemType::loadSuperItem(zItem, zReader);
+    FluidContainerItem* item = dynamic_cast<FluidContainerItem*>(zItem);
+    if (item)
+    {
+        zReader->lese((char*)&item->fluidTypeId, 4);
+        zReader->lese((char*)&item->fluidAmount, 4);
+    }
+    else
+    {
+        std::cout << "ERROR: FluidContainerItemType::loadSuperItem: "
+                     "zItem is not a FluidContainerItem\n";
+    }
+}
+
+void FluidContainerItemType::saveSuperItem(
+    const Item* zItem, Framework::StreamWriter* zWriter) const
+{
+    ItemType::saveSuperItem(zItem, zWriter);
+    const FluidContainerItem* item
+        = dynamic_cast<const FluidContainerItem*>(zItem);
+    if (item)
+    {
+        zWriter->schreibe((char*)&item->fluidTypeId, 4);
+        zWriter->schreibe((char*)&item->fluidAmount, 4);
+    }
+    else
+    {
+        std::cout << "ERROR: FluidContainerItemType::saveSuperItem: "
+                     "zItem is not a FluidContainerItem\n";
+    }
+}
+
+void FluidContainerItemType::loadSuperItemSkill(
+    ItemSkill* zSkill, Framework::StreamReader* zReader) const
+{
+    ItemType::loadSuperItemSkill(zSkill, zReader);
+    FluidContainerItemSkill* skill
+        = dynamic_cast<FluidContainerItemSkill*>(zSkill);
+    if (skill)
+    {
+        zReader->lese((char*)&skill->level, 4);
+        zReader->lese((char*)&skill->xp, 4);
+        zReader->lese((char*)&skill->maxXP, 4);
+    }
+    else
+    {
+        std::cout << "ERROR: FluidContainerItemType::loadSuperItemSkill: "
+                     "zSkill is not a FluidContainerItemSkill\n";
+    }
+}
+
+void FluidContainerItemType::saveSuperItemSkill(
+    const ItemSkill* zSkill, Framework::StreamWriter* zWriter) const
+{
+    ItemType::saveSuperItemSkill(zSkill, zWriter);
+    const FluidContainerItemSkill* skill
+        = dynamic_cast<const FluidContainerItemSkill*>(zSkill);
+    if (skill)
+    {
+        zWriter->schreibe((char*)&skill->level, 4);
+        zWriter->schreibe((char*)&skill->xp, 4);
+        zWriter->schreibe((char*)&skill->maxXP, 4);
+    }
+    else
+    {
+        std::cout << "ERROR: FluidContainerItemType::saveSuperItemSkill: "
+                     "zSkill is not a FluidContainerItemSkill\n";
+    }
+}
+
+FluidContainerItemType::FluidContainerItemType(
+    int typeId, const char* name, ModelInfo model)
+    : ItemType(
+        typeId, name, new FluidContainerItemSkillLevelUpRule(), 0, model),
+      maxFluidAmount(1000)
+{}
+
+Item* FluidContainerItemType::createItem() const
+{
+    Item* result = new FluidContainerItem(getId(), getName());
+    return result;
+}
+
+ItemSkill* FluidContainerItemType::createDefaultItemSkill() const
+{
+    return new FluidContainerItemSkill(getId());
+}
+
+void FluidContainerItemType::setItemAttribute(
+    Item* zItem, Framework::Text name, Framework::JSON::JSONValue* zValue) const
+{
+    FluidContainerItem* item = dynamic_cast<FluidContainerItem*>(zItem);
+    if (!item)
+    {
+        std::cout << "ERROR: FluidContainerItemType::setItemAttribute: "
+                     "zItem is not a FluidContainerItem\n";
+        return;
+    }
+    if (name.istGleich("fluidType"))
+    {
+        if (zValue->getType() == Framework::JSON::JSONType::STRING)
+        {
+            int id = ItemType::getTypeId(zValue->asString()->getString());
+            if (id)
+            {
+                item->fluidTypeId = id;
+            }
+            else
+            {
+                std::cout << "ERROR: FluidContainerItemType::setItemAttribute: "
+                             "'fluidType' is not a valid type name\n";
+            }
+        }
+        else
+        {
+            std::cout << "ERROR: FluidContainerItemType::setItemAttribute: "
+                         "'fluidType' is not a string or string\n";
+        }
+    }
+    else if (name.istGleich("fluidAmount"))
+    {
+        if (zValue->getType() == Framework::JSON::JSONType::NUMBER)
+        {
+            item->fluidAmount = (int)zValue->asNumber()->getNumber();
+        }
+        else
+        {
+            std::cout << "ERROR: FluidContainerItemType::setItemAttribute: "
+                         "'fluidAmount' is not a number\n";
+        }
+    }
+    else
+    {
+        ItemType::setItemAttribute(zItem, name, zValue);
+    }
+}
+
+void FluidContainerItemType::addItemAttributes(
+    Item* zItem, Framework::JSON::JSONObject* zItemObjet) const
+{
+    FluidContainerItem* item = dynamic_cast<FluidContainerItem*>(zItem);
+    if (!item)
+    {
+        std::cout << "ERROR: FluidContainerItemType::addItemAttributes: "
+                     "zItem is not a FluidContainerItem\n";
+        return;
+    }
+    ItemType::addItemAttributes(zItem, zItemObjet);
+    if (item->fluidTypeId)
+    {
+        zItemObjet->addValue("fluidType",
+            new Framework::JSON::JSONString(
+                StaticRegistry<ItemType>::INSTANCE.zElement(item->fluidTypeId)
+                    ->getName()));
+        zItemObjet->addValue(
+            "fluidAmount", new Framework::JSON::JSONNumber(item->fluidAmount));
+    }
+}
+
+int FluidContainerItemType::getMaxFluidAmount() const
+{
+    return maxFluidAmount;
+}
+
+FluidContainerItemType* FluidContainerItemType::setMaxFluidAmount(
+    int maxFluidAmount)
+{
+    this->maxFluidAmount = maxFluidAmount;
+    return this;
+}

+ 87 - 0
FactoryCraft/FluidContainer.h

@@ -0,0 +1,87 @@
+#pragma once
+
+#include <Vec3.h>
+
+#include "Item.h"
+#include "ItemSkill.h"
+
+class FluidContainerItemType;
+
+class FluidContainerItem : public Item
+{
+private:
+    int fluidTypeId;
+    int fluidAmount;
+
+public:
+    FluidContainerItem(int itemTypeId, const char* name);
+    virtual const BlockType* zPlacedBlockType() const override;
+    virtual bool canBeStackedWith(const Item* zItem) const override;
+    virtual bool canBePlacedAt(
+        int dimensionId, Framework::Vec3<int> worldPos) const override;
+    virtual void onPlaced();
+    virtual Framework::Text getTooltipUIML() const override;
+    virtual bool applyFoodEffects(Entity* zTarget) override;
+    virtual bool canApplyFoodEffectsFully(Entity* zTarget) const override;
+
+    int getAmount() const;
+    void setAmount(int amount);
+    int getFluidTypeId() const;
+    void setFluidTypeId(int fluidTypeId);
+
+    friend FluidContainerItemType;
+};
+
+class FluidContainerItemSkillLevelUpRule;
+
+class FluidContainerItemSkill : public ItemSkill
+{
+    int level;
+    float xp;
+    float maxXP;
+
+public:
+    FluidContainerItemSkill(int itemTypeId);
+    virtual bool use(Entity* zActor, Item* zUsedItem, Block* zTarget) override;
+    virtual bool use(Entity* zActor, Item* zUsedItem, Entity* zTarget) override;
+
+    friend FluidContainerItemType;
+    friend FluidContainerItemSkillLevelUpRule;
+};
+
+class FluidContainerItemSkillLevelUpRule : public ItemSkillLevelUpRule
+{
+public:
+    FluidContainerItemSkillLevelUpRule();
+    void applyOn(ItemSkill* zSkill) override;
+};
+
+class FluidContainerItemType : public ItemType
+{
+private:
+    int maxFluidAmount;
+
+protected:
+    virtual void loadSuperItem(
+        Item* zItem, Framework::StreamReader* zReader) const override;
+    virtual void saveSuperItem(
+        const Item* zItem, Framework::StreamWriter* zWriter) const override;
+    virtual void loadSuperItemSkill(
+        ItemSkill* zSkill, Framework::StreamReader* zReader) const override;
+    virtual void saveSuperItemSkill(const ItemSkill* zSkill,
+        Framework::StreamWriter* zWriter) const override;
+
+public:
+    FluidContainerItemType(int typeId, const char* name, ModelInfo model);
+
+    virtual Item* createItem() const override;
+    virtual ItemSkill* createDefaultItemSkill() const override;
+    virtual void setItemAttribute(Item* zItem,
+        Framework::Text name,
+        Framework::JSON::JSONValue* zValue) const override;
+    virtual void addItemAttributes(
+        Item* zItem, Framework::JSON::JSONObject* zItemObjet) const override;
+    int getMaxFluidAmount() const;
+
+    FluidContainerItemType* setMaxFluidAmount(int maxFluidAmount);
+};

+ 3 - 3
FactoryCraft/Game.cpp

@@ -242,7 +242,7 @@ void GameClient::sendTypes()
     client->zForegroundWriter()->schreibe((char*)&count, 4);
     for (int i = 0; i < count; i++)
     {
-        BlockType* t = StaticRegistry<BlockType>::INSTANCE.zElement(i);
+        const BlockType* t = StaticRegistry<BlockType>::INSTANCE.zElement(i);
         t->writeTypeInfo(client->zForegroundWriter());
     }
     count = 0;
@@ -255,7 +255,7 @@ void GameClient::sendTypes()
     {
         if (StaticRegistry<ItemType>::INSTANCE.zElement(i))
         {
-            ItemType* t = StaticRegistry<ItemType>::INSTANCE.zElement(i);
+            const ItemType* t = StaticRegistry<ItemType>::INSTANCE.zElement(i);
             int id = t->getId();
             client->zForegroundWriter()->schreibe((char*)&id, 4);
             char len = (char)t->getName().getLength();
@@ -272,7 +272,7 @@ void GameClient::sendTypes()
     client->zForegroundWriter()->schreibe((char*)&count, 4);
     for (int i = 0; i < count; i++)
     {
-        EntityType* t = StaticRegistry<EntityType>::INSTANCE.zElement(i);
+        const EntityType* t = StaticRegistry<EntityType>::INSTANCE.zElement(i);
         int id = t->getId();
         client->zForegroundWriter()->schreibe((char*)&id, 4);
         t->getModel().writeTo(client->zForegroundWriter());

+ 3 - 1
FactoryCraft/Item.cpp

@@ -126,7 +126,9 @@ bool Item::canBeStackedWith(const Item* zItem) const
 bool Item::canBePlacedAt(int dimensionId, Vec3<int> worldPos) const
 {
     auto b = Game::INSTANCE->zBlockAt(worldPos, dimensionId);
-    return (b.isA() && b.getA()->zBlockType()->getId() == BlockTypeEnum::AIR)
+    return (b.isA()
+               && (b.getA()->zBlockType()->getId() == BlockTypeEnum::AIR
+                   || b.getA()->zBlockType()->isFluid()))
         || (b.isB() && b.getB() == BlockTypeEnum::AIR);
 }
 

+ 1 - 1
FactoryCraft/Item.h

@@ -38,7 +38,7 @@ public:
         std::function<bool(const Item*, Entity*)> destroysItemTest);
     const ItemType* zItemType() const;
     int getTypeId() const;
-    const BlockType* zPlacedBlockType() const;
+    virtual const BlockType* zPlacedBlockType() const;
     float getHp() const;
     float getDurability() const;
     bool isUsable() const;

+ 20 - 4
FactoryCraft/ItemStack.cpp

@@ -27,21 +27,37 @@ ItemStack* ItemStack::split(int size)
 {
     size = MIN(size, this->size);
     this->size -= size;
-    return new ItemStack(
+    ItemStack *result = new ItemStack(
         dynamic_cast<Item*>(item->getThis()), size, item->getMaxStackSize());
+    if (size == 0)
+    {
+        item->release();
+        item = 0;
+    }
+    return result;
 }
 
 Item* ItemStack::extractFromStack()
 {
     if (size == 0) return 0;
     --size;
-    return item->zItemType()->cloneItem(item);
+    Item* result = item->zItemType()->cloneItem(item);
+    if (size == 0)
+    {
+        item->release();
+        item = 0;
+    }
+    return result;
 }
 
 bool ItemStack::addToStack(Item* item)
 {
     if (size < maxSize && (size == 0 || this->item->canBeStackedWith(item)))
     {
+        if (!this->item)
+        {
+            this->item = item->zItemType()->cloneItem(item);
+        }
         ++size;
         item->release();
         return true;
@@ -52,7 +68,7 @@ bool ItemStack::addToStack(Item* item)
 
 void ItemStack::addItemStack(ItemStack* zItemStack)
 {
-    if (zItemStack->zItem()->canBeStackedWith(item))
+    if (item && zItemStack->zItem()->canBeStackedWith(item))
     {
         int numToAdd = MIN(zItemStack->getSize(), maxSize - size);
         size += numToAdd;
@@ -62,7 +78,7 @@ void ItemStack::addItemStack(ItemStack* zItemStack)
 
 bool ItemStack::isStackable(Item* zItem) const
 {
-    return item->canBeStackedWith(zItem);
+    return size == 0 || item->canBeStackedWith(zItem);
 }
 
 const Item* ItemStack::zItem() const

+ 1 - 0
FactoryCraft/ItemType.h

@@ -28,6 +28,7 @@ public:
     static const int FLINT = BlockTypeEnum::MAX_VALUE + 8;
     static const int SHOVEL = BlockTypeEnum::MAX_VALUE + 9;
     static const int SHOVEL_BROKEN = BlockTypeEnum::MAX_VALUE + 10;
+    static const int WOODEN_BUCKET = BlockTypeEnum::MAX_VALUE + 11;
 };
 
 class ItemType : public virtual Framework::ReferenceCounter

+ 1 - 1
FactoryCraft/Recipie.cpp

@@ -32,7 +32,7 @@ Framework::Array<ItemInfo> Recipie::getOutput(CraftingStorage* zStorage) const
     return result;
 }
 
-bool Recipie::createsOutput(ItemType* zType)
+bool Recipie::createsOutput(const ItemType* zType)
 {
     for (const Item* output : outputs)
     {

+ 1 - 1
FactoryCraft/Recipie.h

@@ -32,7 +32,7 @@ public:
     virtual Framework::Text getRecipieUIML() = 0;
     virtual Framework::Array<ItemInfo> getOutput(
         CraftingStorage* zStorage) const;
-    virtual bool createsOutput(ItemType* zType);
+    virtual bool createsOutput(const ItemType* zType);
 };
 
 class UnshapedRecipie : public Recipie

+ 1 - 1
FactoryCraft/RecipieList.cpp

@@ -25,7 +25,7 @@ const Framework::Text& RecipieList::getName() const
 }
 
 void RecipieList::findRecipies(
-    ItemType* zTargetType, Framework::RCArray<Recipie>& recipies)
+    const ItemType* zTargetType, Framework::RCArray<Recipie>& recipies)
 {
     for (Recipie* recipie : this->recipies)
     {

+ 1 - 1
FactoryCraft/RecipieList.h

@@ -15,5 +15,5 @@ public:
     void addRecipie(Recipie* recipie);
     Recipie* zFirstRecipie(CraftingStorage* zStorage);
     const Framework::Text& getName() const;
-    void findRecipies(ItemType *zTargetType, Framework::RCArray<Recipie>& recipies);
+    void findRecipies(const ItemType *zTargetType, Framework::RCArray<Recipie>& recipies);
 };

+ 1 - 1
FactoryCraft/RecipieLoader.cpp

@@ -218,7 +218,7 @@ void RecipieLoader::registerRecipieList(const char* name)
     lists.add(new RecipieList(name));
 }
 
-Framework::Text RecipieLoader::getCrafingUIML(ItemType* zTargetType)
+Framework::Text RecipieLoader::getCrafingUIML(const ItemType* zTargetType)
 {
     Framework::Text result = "<dialog id=\"crafting_";
     result.append() << zTargetType->getId() << "\"><craftingRecipies>";

+ 1 - 1
FactoryCraft/RecipieLoader.h

@@ -15,7 +15,7 @@ public:
     void loadRecipies(const char* path);
     RecipieList* zRecipieList(const char* name) const;
     void registerRecipieList(const char* name);
-    Framework::Text getCrafingUIML(ItemType* zTargetType);
+    Framework::Text getCrafingUIML(const ItemType* zTargetType);
 
 private:
     void loadRecipie(Framework::JSON::JSONObject* zRecipie);

+ 15 - 1
FactoryCraft/StaticInitializerOrder.cpp

@@ -18,6 +18,7 @@
 // item skills
 #include "Axe.h"
 #include "BasicItems.h"
+#include "FluidContainer.h"
 #include "Hoe.h"
 #include "PlayerHand.h"
 #include "Shovel.h"
@@ -284,7 +285,17 @@ void initializeBlockTypes()
          ModelInfo("fluid", "fluids.ltdb/water.png", 6),
          "Water",
          0xFF2323BF,
-         Vec3<float>(0.8f, 0.8f, 0.95f), 20, 8))
+         Vec3<float>(0.8f, 0.8f, 0.95f),
+         20,
+         8))
+        ->setFoodEffect([](FluidContainerItem* zItem, Entity* zUser) {
+            int drinkable = (int)(zUser->getMaxThirst() - zUser->getThirst());
+            if (zItem->getAmount() < drinkable) drinkable = zItem->getAmount();
+            if (!drinkable) return false;
+            zItem->setAmount(zItem->getAmount() - drinkable);
+            zUser->setThirst(zUser->getThirst() + drinkable);
+            return true;
+        })
         ->initializeDefault();
     (new BasicBlockType(BlockTypeEnum::CRAFTING_TABLE,
          ItemTypeEnum::CRAFTING_TABLE,
@@ -666,6 +677,9 @@ void initializeItemTypes()
                 0,
                 10);
         }));
+    new FluidContainerItemType(ItemTypeEnum::WOODEN_BUCKET,
+        "Wooden Bucket",
+        ModelInfo("items.m3/bucket", "blocks.ltdb/woodplanks.png", 1));
 }
 
 void initializeEntityTypes()

+ 1 - 1
FactoryCraft/StaticRegistry.h

@@ -46,7 +46,7 @@ public:
         registry[id] = type;
     }
 
-    T* zElement(int id)
+    const T* zElement(int id)
     {
         if (id < 0 || id >= count) return 0;
         return registry[id];

+ 2 - 0
Windows Version/Windows Version.vcxproj

@@ -190,6 +190,7 @@ copy ..\..\..\..\..\Allgemein\Framework\x64\release\Framework.dll Framework.dll<
     <ClCompile Include="..\FactoryCraft\FastNoiseWrapper.cpp" />
     <ClCompile Include="..\FactoryCraft\FlattenNoise.cpp" />
     <ClCompile Include="..\FactoryCraft\FluidBlock.cpp" />
+    <ClCompile Include="..\FactoryCraft\FluidContainer.cpp" />
     <ClCompile Include="..\FactoryCraft\Game.cpp" />
     <ClCompile Include="..\FactoryCraft\GeneratedStructure.cpp" />
     <ClCompile Include="..\FactoryCraft\GeneratorTemplate.cpp" />
@@ -260,6 +261,7 @@ copy ..\..\..\..\..\Allgemein\Framework\x64\release\Framework.dll Framework.dll<
     <ClInclude Include="..\FactoryCraft\BlockInstanceGeneratorRule.h" />
     <ClInclude Include="..\FactoryCraft\FactorizeNoise.h" />
     <ClInclude Include="..\FactoryCraft\FlattenNoise.h" />
+    <ClInclude Include="..\FactoryCraft\FluidContainer.h" />
     <ClInclude Include="..\FactoryCraft\GeneratorRule.h" />
     <ClInclude Include="..\FactoryCraft\BlockTypeGeneratorRule.h" />
     <ClInclude Include="..\FactoryCraft\Chest.h" />

+ 9 - 0
Windows Version/Windows Version.vcxproj.filters

@@ -103,6 +103,9 @@
     <Filter Include="UI">
       <UniqueIdentifier>{a59326ac-f962-411e-b62d-6ad23ddd6506}</UniqueIdentifier>
     </Filter>
+    <Filter Include="inventory\items\fluidContainer">
+      <UniqueIdentifier>{6e68779b-e8d4-48be-84dc-4d4623a8fd85}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\FactoryCraft\Server.cpp">
@@ -396,6 +399,9 @@
     <ClCompile Include="..\FactoryCraft\QuestDialog.cpp">
       <Filter>quests</Filter>
     </ClCompile>
+    <ClCompile Include="..\FactoryCraft\FluidContainer.cpp">
+      <Filter>inventory\items\fluidContainer</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\FactoryCraft\Chunk.h">
@@ -701,5 +707,8 @@
     <ClInclude Include="..\FactoryCraft\QuestDialog.h">
       <Filter>quests</Filter>
     </ClInclude>
+    <ClInclude Include="..\FactoryCraft\FluidContainer.h">
+      <Filter>inventory\items\fluidContainer</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>

+ 366 - 292
Windows Version/data/recipies/basicItems.json

@@ -1,317 +1,391 @@
 [
-    {
-        "group": "inventory",
-        "height": 2,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 0
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 1
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 1,
-                "y": 0
-            }
-        ],
-        "output": {
-            "itemType": "Flint"
+  {
+    "group": "inventory",
+    "height": 2,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
         },
-        "type": "shaped",
-        "width": 2
+        "x": 0,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 1,
+        "y": 0
+      }
+    ],
+    "output": {
+      "itemType": "Flint"
     },
-    {
-        "group": "inventory",
-        "height": 2,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 1,
-                "y": 1
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 1
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 1,
-                "y": 0
-            }
-        ],
-        "output": {
-            "itemType": "Flint"
+    "type": "shaped",
+    "width": 2
+  },
+  {
+    "group": "inventory",
+    "height": 2,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 1,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 0,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
         },
-        "type": "shaped",
-        "width": 2
+        "x": 1,
+        "y": 0
+      }
+    ],
+    "output": {
+      "itemType": "Flint"
     },
-    {
-        "group": "inventory",
-        "height": 2,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 0
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 1,
-                "y": 1
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 1,
-                "y": 0
-            }
-        ],
-        "output": {
-            "itemType": "Flint"
+    "type": "shaped",
+    "width": 2
+  },
+  {
+    "group": "inventory",
+    "height": 2,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Gravel"
         },
-        "type": "shaped",
-        "width": 2
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 1,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 1,
+        "y": 0
+      }
+    ],
+    "output": {
+      "itemType": "Flint"
     },
-    {
-        "group": "inventory",
-        "height": 2,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 0
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 1
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 1,
-                "y": 1
-            }
-        ],
-        "output": {
-            "itemType": "Flint"
+    "type": "shaped",
+    "width": 2
+  },
+  {
+    "group": "inventory",
+    "height": 2,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
         },
-        "type": "shaped",
-        "width": 2
+        "x": 0,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 1,
+        "y": 1
+      }
+    ],
+    "output": {
+      "itemType": "Flint"
     },
-    {
-        "group": "inventory",
-        "height": 1,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 0
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 1,
-                "y": 0
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 2,
-                "y": 0
-            }
-        ],
-        "output": {
-            "itemType": "Flint"
+    "type": "shaped",
+    "width": 2
+  },
+  {
+    "group": "inventory",
+    "height": 1,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 1,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
         },
-        "type": "shaped",
-        "width": 3
+        "x": 2,
+        "y": 0
+      }
+    ],
+    "output": {
+      "itemType": "Flint"
     },
-    {
-        "group": "inventory",
-        "height": 3,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 0
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 1
-            },
-            {
-                "filter": {
-                    "itemType": "Gravel"
-                },
-                "x": 0,
-                "y": 2
-            }
-        ],
-        "output": {
-            "itemType": "Flint"
+    "type": "shaped",
+    "width": 3
+  },
+  {
+    "group": "inventory",
+    "height": 3,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Gravel"
         },
-        "type": "shaped",
-        "width": 1
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 0,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "Gravel"
+        },
+        "x": 0,
+        "y": 2
+      }
+    ],
+    "output": {
+      "itemType": "Flint"
     },
-    {
-        "group": "inventory",
-        "height": 2,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Flint"
-                },
-                "x": 0,
-                "y": 0
+    "type": "shaped",
+    "width": 1
+  },
+  {
+    "group": "inventory",
+    "height": 2,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Flint"
+        },
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "left": {
+            "left": {
+              "itemType": "Oak"
             },
-            {
-                "filter": {
-                    "left": {
-                        "left": {
-                            "itemType": "Oak"
-                        },
-                        "operator": "||",
-                        "right": {
-                            "itemType": "Birch"
-                        }
-                    },
-                    "operator": "||",
-                    "right": {
-                        "itemType": "Beech"
-                    }
-                },
-                "x": 0,
-                "y": 1
+            "operator": "||",
+            "right": {
+              "itemType": "Birch"
             }
-        ],
-        "output": {
-            "itemType": "WoodenStick"
+          },
+          "operator": "||",
+          "right": {
+            "itemType": "Beech"
+          }
         },
-        "type": "shaped",
-        "width": 1
+        "x": 0,
+        "y": 1
+      }
+    ],
+    "output": {
+      "itemType": "WoodenStick"
     },
-    {
-        "group": "inventory",
-        "height": 2,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Flint"
-                },
-                "x": 0,
-                "y": 0
-            },
-            {
-                "filter": {
-                    "itemType": "Pine"
-                },
-                "x": 0,
-                "y": 1
-            }
-        ],
-        "output": {
-            "itemType": "Resin"
+    "type": "shaped",
+    "width": 1
+  },
+  {
+    "group": "inventory",
+    "height": 2,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Flint"
+        },
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "Pine"
+        },
+        "x": 0,
+        "y": 1
+      }
+    ],
+    "output": {
+      "itemType": "Resin"
+    },
+    "type": "shaped",
+    "width": 1
+  },
+  {
+    "group": "inventory",
+    "height": 2,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "Resin"
+        },
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "WoodenStick"
         },
-        "type": "shaped",
-        "width": 1
+        "x": 0,
+        "y": 1
+      }
+    ],
+    "output": {
+      "itemType": "Torch"
     },
-    {
-        "group": "inventory",
-        "height": 2,
-        "inputs": [
-            {
-                "filter": {
-                    "itemType": "Resin"
-                },
-                "x": 0,
-                "y": 0
+    "type": "shaped",
+    "width": 1
+  },
+  {
+    "group": "inventory",
+    "height": 1,
+    "inputs": [
+      {
+        "filter": {
+          "left": {
+            "left": {
+              "itemType": "Beech Leaves"
             },
-            {
-                "filter": {
-                    "itemType": "WoodenStick"
-                },
-                "x": 0,
-                "y": 1
+            "operator": "||",
+            "right": {
+              "itemType": "Birch Leaves"
             }
-        ],
-        "output": {
-            "itemType": "Torch"
+          },
+          "operator": "||",
+          "right": {
+            "left": {
+              "itemType": "Oak Leaves"
+            },
+            "operator": "||",
+            "right": {
+              "itemType": "Pine Leaves"
+            }
+          }
         },
-        "type": "shaped",
-        "width": 1
+        "x": 0,
+        "y": 0
+      }
+    ],
+    "output": {
+      "itemType": "WoodenStick"
     },
-    {
-        "group": "inventory",
-        "height": 1,
-        "inputs": [
-            {
-                "filter": {
-                    "left": {
-                        "left": {
-                            "itemType": "Beech Leaves"
-                        },
-                        "operator": "||",
-                        "right": {
-                            "itemType": "Birch Leaves"
-                        }
-                    },
-                    "operator": "||",
-                    "right": {
-                        "left": {
-                            "itemType": "Oak Leaves"
-                        },
-                        "operator": "||",
-                        "right": {
-                            "itemType": "Pine Leaves"
-                        }
-                    }
-                },
-                "x": 0,
-                "y": 0
-            }
-        ],
-        "output": {
-            "itemType": "WoodenStick"
+    "type": "shaped",
+    "width": 1
+  },
+  {
+    "group": "inventory",
+    "height": 3,
+    "inputs": [
+      {
+        "filter": {
+          "itemType": "WoodenStick"
+        },
+        "x": 0,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "WoodenStick"
+        },
+        "x": 0,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "WoodenStick"
+        },
+        "x": 0,
+        "y": 2
+      },
+      {
+        "filter": {
+          "itemType": "WoodenStick"
         },
-        "type": "shaped",
-        "width": 1
-    }
+        "x": 1,
+        "y": 2
+      },
+      {
+        "filter": {
+          "itemType": "WoodenStick"
+        },
+        "x": 2,
+        "y": 2
+      },
+      {
+        "filter": {
+          "itemType": "WoodenStick"
+        },
+        "x": 2,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "WoodenStick"
+        },
+        "x": 2,
+        "y": 0
+      },
+      {
+        "filter": {
+          "itemType": "Flint"
+        },
+        "x": 1,
+        "y": 1
+      },
+      {
+        "filter": {
+          "itemType": "Flint"
+        },
+        "x": 1,
+        "y": 0
+      }
+    ],
+    "output": {
+      "itemType": "Wooden Bucket"
+    },
+    "type": "shaped",
+    "width": 3
+  }
 ]