Initial commit

This commit is contained in:
Astatin3
2024-04-30 22:07:50 -06:00
commit 8565caa62a
8463 changed files with 4915934 additions and 0 deletions
@@ -0,0 +1,883 @@
#include "DispenserFarmTasks.hpp"
#include "botcraft/Game/World/World.hpp"
#include "botcraft/Game/Inventory/InventoryManager.hpp"
#include "botcraft/Game/Inventory/Window.hpp"
#include "botcraft/Game/Entities/EntityManager.hpp"
#include "botcraft/Game/Entities/LocalPlayer.hpp"
#include "botcraft/Game/Entities/entities/npc/VillagerEntity.hpp"
#include "botcraft/Game/Entities/entities/item/ItemEntity.hpp"
#include "botcraft/Game/AssetsManager.hpp"
#include "botcraft/Utilities/Logger.hpp"
#include "botcraft/AI/Tasks/AllTasks.hpp"
using namespace Botcraft;
using namespace ProtocolCraft;
Status InitializeBlocks(BehaviourClient& client, const int radius)
{
Blackboard& b = client.GetBlackboard();
Position bed_position;
Position crafting_table_position;
Position buying_standing_position;
Position cactus_position;
Position cactus_standing_position;
Position output_shulker_position;
Position bones_shulker_position;
Position rotten_flesh_shulker_position;
Position stone_shulker_position;
Position stone_standing_position;
Position note_block_position;
Position despawn_position;
Position stone_lever_position;
std::vector<Position> potato_positions;
std::vector<Position> carrot_positions;
std::vector<Position> stone_positions;
const Position my_pos = client.GetLocalPlayer()->GetPosition();
std::shared_ptr<World> world = client.GetWorld();
Position current_pos;
for (int x = -radius; x < radius + 1; ++x)
{
current_pos.x = my_pos.x + x;
for (int y = std::max(-radius, world->GetMinY()); y < std::min(world->GetMinY() + world->GetHeight(), radius + 1); ++y)
{
current_pos.y = my_pos.y + y;
for (int z = -radius; z < radius + 1; ++z)
{
current_pos.z = my_pos.z + z;
const Blockstate* block = world->GetBlock(current_pos);
if (block == nullptr)
{
continue;
}
const std::string& block_name = block->GetName();
if (block_name == "minecraft:red_bed")
{
bed_position = current_pos;
LOG_INFO("Bed found at: " << current_pos << "!");
despawn_position = current_pos + Position(0, 0, 140);
LOG_INFO("Despawn position found at: " << current_pos + Position(0, 0, 140) << "!");
}
else if (block_name == "minecraft:crafting_table")
{
crafting_table_position = current_pos;
buying_standing_position = current_pos + Position(0, 1, 0);
LOG_INFO("Crafting table found at: " << current_pos << "!");
LOG_INFO("Buying standing found at: " << current_pos + Position(0, 1, 0) << "!");
}
else if (block_name == "minecraft:cactus")
{
cactus_position = current_pos;
LOG_INFO("Cactus found at: " << current_pos << "!");
}
else if (block_name == "minecraft:yellow_concrete")
{
cactus_standing_position = current_pos + Position(0, 1, 0);
LOG_INFO("Cactus standing found at: " << current_pos + Position(0, 1, 0) << "!");
}
else if (block_name == "minecraft:red_shulker_box")
{
output_shulker_position = current_pos;
LOG_INFO("Output shulker position found at: " << current_pos << "!");
}
else if (block_name == "minecraft:white_shulker_box")
{
bones_shulker_position = current_pos;
LOG_INFO("Bones shulker position found at: " << current_pos << "!");
}
else if (block_name == "minecraft:orange_shulker_box")
{
rotten_flesh_shulker_position = current_pos;
LOG_INFO("Rotten flesh shulker position found at: " << current_pos << "!");
}
else if (block_name == "minecraft:light_gray_shulker_box")
{
stone_shulker_position = current_pos;
LOG_INFO("Stone shulker position found at: " << current_pos << "!");
}
else if (block_name == "minecraft:light_gray_concrete")
{
stone_standing_position = current_pos + Position(0, 1, 0);
LOG_INFO("Stone standing position found at: " << current_pos + Position(0, 1, 0) << "!");
for (int i = 1; i < 5; ++i)
{
stone_positions.push_back(current_pos + Position(0, 2, i));
LOG_INFO("Stone position found at: " << current_pos + Position(0, 2, i) << "!");
}
}
else if (block_name == "minecraft:lever")
{
stone_lever_position = current_pos;
LOG_INFO("Stone lever position found at: " << current_pos << "!");
}
else if (block_name == "minecraft:note_block")
{
note_block_position = current_pos;
LOG_INFO("Note block position found at: " << current_pos << "!");
}
else if (block_name == "minecraft:potatoes")
{
potato_positions.push_back(current_pos);
LOG_INFO("Potatoes found at: " << current_pos << "!");
}
else if (block_name == "minecraft:carrots")
{
carrot_positions.push_back(current_pos);
LOG_INFO("Carrots found at: " << current_pos << "!");
}
}
}
}
// Sort stone positions
std::sort(stone_positions.begin(), stone_positions.end(), [&](const Position& a, const Position& b)
{
return a.SqrDist(stone_standing_position) < b.SqrDist(stone_standing_position);
});
// Sort potato and carrot positions
std::sort(potato_positions.begin(), potato_positions.end(), [&](const Position& a, const Position& b)
{
return a.SqrDist(stone_standing_position) < b.SqrDist(stone_standing_position);
});
std::sort(carrot_positions.begin(), carrot_positions.end(), [&](const Position& a, const Position& b)
{
return a.SqrDist(stone_standing_position) < b.SqrDist(stone_standing_position);
});
std::vector<int> farmer_id;
std::vector<int> fletcher_id;
std::vector<int> cleric_id;
std::vector<int> shepperd_id;
std::vector<int> toolsmith_id;
{
std::shared_ptr<EntityManager> entity_manager = client.GetEntityManager();
auto entities = entity_manager->GetEntities();
for (const auto& [id, entity] : *entities)
{
if (entity->GetType() == EntityType::Villager)
{
std::shared_ptr<VillagerEntity> villager = std::static_pointer_cast<VillagerEntity>(entity);
if (villager->GetDataVillagerData().profession == 4)
{
cleric_id.push_back(id);
LOG_INFO("Cleric found with id: " << id);
}
else if (villager->GetDataVillagerData().profession == 5)
{
farmer_id.push_back(id);
LOG_INFO("Farmer found with id: " << id);
}
else if (villager->GetDataVillagerData().profession == 7)
{
fletcher_id.push_back(id);
LOG_INFO("Fletcher found with id: " << id);
}
else if (villager->GetDataVillagerData().profession == 12)
{
shepperd_id.push_back(id);
LOG_INFO("Shepperd found with id: " << id);
}
else if (villager->GetDataVillagerData().profession == 13)
{
toolsmith_id.push_back(id);
LOG_INFO("Toolsmith found with id: " << id);
}
}
}
}
b.Set("DispenserFarmBot.bed_position", bed_position);
b.Set("DispenserFarmBot.crafting_table_position", crafting_table_position);
b.Set("DispenserFarmBot.buying_standing_position", buying_standing_position);
b.Set("DispenserFarmBot.cactus_position", cactus_position);
b.Set("DispenserFarmBot.cactus_standing_position", cactus_standing_position);
b.Set("DispenserFarmBot.output_shulker_position", output_shulker_position);
b.Set("DispenserFarmBot.bones_shulker_position", bones_shulker_position);
b.Set("DispenserFarmBot.rotten_flesh_shulker_position", rotten_flesh_shulker_position);
b.Set("DispenserFarmBot.stone_shulker_position", stone_shulker_position);
b.Set("DispenserFarmBot.stone_standing_position", stone_standing_position);
b.Set("DispenserFarmBot.note_block_position", note_block_position);
b.Set("DispenserFarmBot.despawn_position", despawn_position);
b.Set("DispenserFarmBot.stone_lever_position", stone_lever_position);
b.Set("DispenserFarmBot.potato_positions", potato_positions);
b.Set("DispenserFarmBot.carrot_positions", carrot_positions);
b.Set("DispenserFarmBot.stone_positions", stone_positions);
b.Set("DispenserFarmBot.farmer_id", farmer_id);
b.Set("DispenserFarmBot.fletcher_id", fletcher_id);
b.Set("DispenserFarmBot.cleric_id", cleric_id);
b.Set("DispenserFarmBot.shepperd_id", shepperd_id);
b.Set("DispenserFarmBot.toolsmith_id", toolsmith_id);
b.Set("DispenserFarmBot.initialized", true);
return Status::Success;
}
Status StartSpawners(BehaviourClient& client)
{
std::shared_ptr<World> world = client.GetWorld();
Blackboard& blackboard = client.GetBlackboard();
const Position& lever_pos = blackboard.Get<Position>("DispenserFarmBot.stone_lever_position");
const Blockstate* block = world->GetBlock(lever_pos);
if (block == nullptr || block->GetName() != "minecraft:lever")
{
LOG_WARNING("Error trying to start spawners, no lever at " << lever_pos);
return Status::Failure;
}
if (block->GetVariableValue("powered") == "true")
{
return Status::Success;
}
// PULL THE LEVER, KRONK!
if (InteractWithBlock(client, lever_pos, Direction::Down, true) == Status::Failure)
{
LOG_WARNING("Error trying to pull the lever during spawners activation");
return Status::Failure;
}
for (int i = 0; i < 100; ++i)
{
client.Yield();
}
return Status::Success;
}
Status CheckFrostWalkerMob(BehaviourClient& client)
{
std::shared_ptr<EntityManager> entity_manager = client.GetEntityManager();
auto entities = entity_manager->GetEntities();
for (const auto& [id, entity] : *entities)
{
if (entity->IsMonster())
{
const Slot boots = entity->GetEquipment(EquipmentSlot::Boots);
if (!boots.IsEmptySlot())
{
if (boots.GetNBT().contains("Enchantments") && boots.GetNBT()["Enchantments"].is_list_of<NBT::TagCompound>())
{
for (const auto& enchantment : boots.GetNBT()["Enchantments"].as_list_of<NBT::TagCompound>())
{
if (enchantment.contains("id") &&
enchantment["id"].is<std::string>() &&
enchantment["id"].get<std::string>() == "minecraft:frost_walker")
{
return Status::Success;
}
}
}
}
}
}
return Status::Failure;
}
Status CleanChest(BehaviourClient& client, const std::string& chest_pos_blackboard, const std::string& item_to_keep)
{
Blackboard& blackboard = client.GetBlackboard();
// Set input parameters for debugger
blackboard.Set<std::string>("CleanChest.chest_pos_blackboard", chest_pos_blackboard);
blackboard.Set<std::string>("CleanChest.item_to_keep", item_to_keep);
std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
const Position& pos = blackboard.Get<Position>(chest_pos_blackboard);
const Position& cactus_standing_pos = blackboard.Get<Position>("DispenserFarmBot.cactus_standing_position");
const Position& cactus_pos = blackboard.Get<Position>("DispenserFarmBot.cactus_position");
bool has_wrong_items = true;
while (has_wrong_items)
{
if (OpenContainer(client, pos) == Status::Failure)
{
LOG_WARNING("Failed to open container for cleaning");
return Status::Failure;
}
for (int i = 0; i < 100; ++i)
{
client.Yield();
}
const short container_id = inventory_manager->GetFirstOpenedWindowId();
const std::shared_ptr<Window> container = inventory_manager->GetWindow(container_id);
if (container == nullptr)
{
LOG_WARNING("Container closed during chest cleaning");
return Status::Failure;
}
std::vector<short> slots_to_remove;
std::vector<short> free_slots_inventory;
std::vector<short> trash_inventory_slots;
// Get slots to remove and free slots in inventory
for (const auto& [id, slot] : container->GetSlots())
{
if (id < container->GetFirstPlayerInventorySlot() && !slot.IsEmptySlot() &&
#if PROTOCOL_VERSION < 340 /* < 1.12.2 */
AssetsManager::getInstance().Items().at(slot.GetBlockID()).at(slot.GetItemDamage())->GetName() != item_to_keep
#else
AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName() != item_to_keep
#endif
)
{
slots_to_remove.push_back(id);
}
else if (id >= container->GetFirstPlayerInventorySlot() &&
slot.IsEmptySlot())
{
free_slots_inventory.push_back(id);
}
}
if (free_slots_inventory.size() == 0)
{
LOG_WARNING("Can't clean chest, no free slot in inventory to take items");
CloseContainer(client, container_id);
return Status::Failure;
}
// Go through as many slots as possible and transfer them in the inventory
int index = 0;
while (index < slots_to_remove.size() && index < free_slots_inventory.size())
{
if (SwapItemsInContainer(client, container_id, slots_to_remove[index], free_slots_inventory[index]) == Status::Failure)
{
LOG_WARNING("Error trying to swap transfer items from chest to inventory");
CloseContainer(client, container_id);
return Status::Failure;
}
trash_inventory_slots.push_back(free_slots_inventory[index] - container->GetFirstPlayerInventorySlot() + 9/*Window::INVENTORY_STORAGE_START*/);
index++;
}
CloseContainer(client, container_id);
if (trash_inventory_slots.size() == 0)
{
return Status::Success;
}
has_wrong_items = index < slots_to_remove.size();
// Now go to the cactus to destroy them
if (GoTo(client, cactus_standing_pos, 0, 0, 0) == Status::Failure)
{
LOG_WARNING("Error trying to go to the cactus standing position");
return Status::Failure;
}
LookAt(client, Vector3<double>(0.5) + cactus_pos, false);
for (int i = 0; i < trash_inventory_slots.size(); ++i)
{
if (DropItemsFromContainer(client, 0/*Window::PLAYER_INVENTORY_INDEX*/, trash_inventory_slots[i]) == Status::Failure)
{
LOG_WARNING("Error trying to drop the items on the cactus");
return Status::Failure;
}
for (int i = 0; i < 50; ++i)
{
client.Yield();
}
}
}
return Status::Success;
}
Status StoreDispenser(BehaviourClient& client)
{
Blackboard& b = client.GetBlackboard();
std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
if (OpenContainer(client, b.Get<Position>("DispenserFarmBot.output_shulker_position")) == Status::Failure)
{
LOG_WARNING("Can't open output chest to store crafted dispenser");
return Status::Failure;
}
for (int i = 0; i < 100; ++i)
{
client.Yield();
}
const short container_id = inventory_manager->GetFirstOpenedWindowId();
const std::shared_ptr<Window> container = inventory_manager->GetWindow(container_id);
if (container == nullptr)
{
LOG_WARNING("Container closed during dispenser storing");
return Status::Failure;
}
short dst_slot = -1;
short src_slot = -1;
const auto dispenser_id = AssetsManager::getInstance().GetItemID("minecraft:dispenser");
for (const auto& [idx, slot] : container->GetSlots())
{
if (dst_slot == -1 && idx < container->GetFirstPlayerInventorySlot() &&
(slot.IsEmptySlot() || (
#if PROTOCOL_VERSION < 340 /* < 1.12.2 */
slot.GetBlockID() == dispenser_id.first && slot.GetItemDamage() == dispenser_id.second
#else
slot.GetItemID() == dispenser_id
#endif
&& slot.GetItemCount() < AssetsManager::getInstance().Items().at(dispenser_id)->GetStackSize() - 1))
)
{
dst_slot = idx;
}
else if (src_slot == -1 && idx >= container->GetFirstPlayerInventorySlot() &&
#if PROTOCOL_VERSION < 340 /* < 1.12.2 */
slot.GetBlockID() == dispenser_id.first && slot.GetItemDamage() == dispenser_id.second
#else
slot.GetItemID() == dispenser_id
#endif
)
{
src_slot = idx;
}
else if (src_slot != -1 && dst_slot != -1)
{
break;
}
}
// No dispenser in inventory, nothing to do
if (src_slot == -1)
{
CloseContainer(client);
return Status::Success;
}
if (dst_slot == -1)
{
LOG_WARNING("Can't find a place for the dispenser in the chest");
CloseContainer(client);
return Status::Failure;
}
if (PutOneItemInContainerSlot(client, container_id, src_slot, dst_slot) == Status::Failure)
{
LOG_WARNING("Error trying to transfer dispenser into chest");
return Status::Failure;
}
CloseContainer(client);
return Status::Success;
}
Status MineCobblestone(BehaviourClient& client)
{
std::shared_ptr<World> world = client.GetWorld();
Blackboard& blackboard = client.GetBlackboard();
const std::vector<Position>& stone_positions = blackboard.Get<std::vector<Position>>("DispenserFarmBot.stone_positions");
const Position* mining_pos = nullptr;
{
for (int i = 0; i < stone_positions.size(); ++i)
{
const Blockstate* block = world->GetBlock(stone_positions[i]);
// Depending on the tick on which lava flows we could also get some cobble
if (block != nullptr && (block->GetName() == "minecraft:stone" || block->GetName() == "minecraft:cobblestone"))
{
mining_pos = stone_positions.data() + i;
break;
}
}
}
// No stone, trigger the note block
if (!mining_pos)
{
const Position& note_block_pos = blackboard.Get<Position>("DispenserFarmBot.note_block_position");
if (InteractWithBlock(client, note_block_pos, Direction::Down, true) == Status::Failure)
{
LOG_WARNING("Error trying to activate the stone note block");
return Status::Failure;
}
for (int i = 0; i < 50; ++i)
{
client.Yield();
}
}
// Wait until we have one stone available (should be quick)
auto start = std::chrono::steady_clock::now();
while (!mining_pos)
{
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 5000)
{
LOG_WARNING("Error waiting for stone block (Timeout).");
return Status::Failure;
}
for (int i = 0; i < stone_positions.size(); ++i)
{
const Blockstate* block = world->GetBlock(stone_positions[i]);
// Depending on the tick on which lava flows we could also get some cobble
if (block != nullptr && (block->GetName() == "minecraft:stone" || block->GetName() == "minecraft:cobblestone"))
{
mining_pos = stone_positions.data() + i;
break;
}
}
if (!mining_pos)
{
client.Yield();
}
}
if (Dig(client, *mining_pos, true, Direction::North) == Status::Failure)
{
LOG_WARNING("Error digging stone.");
return Status::Failure;
}
const Position& stone_shulker_position = blackboard.Get<Position>("DispenserFarmBot.stone_shulker_position");
if (OpenContainer(client, stone_shulker_position) == Status::Failure)
{
LOG_WARNING("Error trying to open stone shulker.");
return Status::Failure;
}
std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
const short container_id = inventory_manager->GetFirstOpenedWindowId();
const std::shared_ptr<Window> shulker_container = inventory_manager->GetWindow(container_id);
if (shulker_container == nullptr)
{
LOG_WARNING("Container closed during stone gathering");
return Status::Failure;
}
short src_slot = -1;
short dst_slot = -1;
start = std::chrono::steady_clock::now();
// Wait for a cobblestone stack to be in the shulker
while (src_slot == -1)
{
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 5000)
{
LOG_WARNING("Error waiting for stone item in shulker (Timeout).");
CloseContainer(client, container_id);
return Status::Failure;
}
dst_slot = -1;
int quantity = 0;
for (const auto& [idx, slot] : shulker_container->GetSlots())
{
if (src_slot == -1 && idx < shulker_container->GetFirstPlayerInventorySlot())
{
#if PROTOCOL_VERSION < 340 /* < 1.12.2 */
if (!slot.IsEmptySlot() && AssetsManager::getInstance().Items().at(slot.GetBlockID()).at(slot.GetItemDamage())->GetName() == "minecraft:cobblestone")
#else
if (!slot.IsEmptySlot() && AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName() == "minecraft:cobblestone")
#endif
{
src_slot = idx;
quantity = slot.GetItemCount();
if (dst_slot != -1)
{
break;
}
}
}
if (src_slot != -1 && idx >= shulker_container->GetFirstPlayerInventorySlot())
{
if (slot.IsEmptySlot() || (slot.GetItemCount() < 64 - quantity &&
#if PROTOCOL_VERSION < 340 /* < 1.12.2 */
AssetsManager::getInstance().Items().at(slot.GetBlockID()).at(slot.GetItemDamage())->GetName() == "minecraft:cobblestone"
#else
AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName() == "minecraft:cobblestone"
#endif
))
{
dst_slot = idx;
break;
}
}
}
if (src_slot != -1 && dst_slot == -1)
{
LOG_WARNING("Error trying to take stone from shulker, no slot available");
CloseContainer(client, container_id);
return Status::Failure;
}
if (src_slot != -1 && dst_slot != -1)
{
break;
}
client.Yield();
}
if (SwapItemsInContainer(client, container_id, src_slot, dst_slot) == Status::Failure)
{
LOG_WARNING("Error getting the stone from the shulker");
CloseContainer(client, container_id);
return Status::Failure;
}
CloseContainer(client, container_id);
return Status::Success;
}
Status TakeFromChest(BehaviourClient& client, const std::string& item_name, const int N)
{
Blackboard& blackboard = client.GetBlackboard();
// Set input parameters for debugger
blackboard.Set<std::string>("TakeFromChest.item_name", item_name);
blackboard.Set<int>("TakeFromChest.N", N);
std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
const short container_id = inventory_manager->GetFirstOpenedWindowId();
const std::shared_ptr<Window> container = inventory_manager->GetWindow(container_id);
if (container == nullptr)
{
LOG_WARNING("Container closed during in chest item taking");
return Status::Failure;
}
std::vector<short> available_slots;
std::vector<short> to_take_slots;
int num_in_inventory = 0;
for (const auto& [idx, slot] : container->GetSlots())
{
if (!slot.IsEmptySlot() &&
#if PROTOCOL_VERSION < 340 /* < 1.12.2 */
AssetsManager::getInstance().Items().at(slot.GetBlockID()).at(slot.GetItemDamage())->GetName() == item_name
#else
AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName() == item_name
#endif
)
{
if (idx < container->GetFirstPlayerInventorySlot())
{
to_take_slots.push_back(idx);
}
else
{
num_in_inventory += slot.GetItemCount();
}
}
else if (idx >= container->GetFirstPlayerInventorySlot() &&
slot.IsEmptySlot())
{
available_slots.push_back(idx);
}
}
int index = 0;
while (num_in_inventory < N && index < to_take_slots.size() && index < available_slots.size())
{
if (SwapItemsInContainer(client, container_id, to_take_slots[index], available_slots[index]) == Status::Failure)
{
LOG_WARNING("Error trying to take an item from a chest");
return Status::Failure;
}
num_in_inventory += container->GetSlot(available_slots[index]).GetItemCount();
index += 1;
}
return num_in_inventory >= N ? Status::Success : Status::Failure;
}
Status CollectCropsAndReplant(BehaviourClient& client, const std::string& blocks_pos_blackboard, const std::string& item_name)
{
Blackboard& blackboard = client.GetBlackboard();
// Set input parameters for debugger
blackboard.Set<std::string>("CollectCropsAndReplant.blocks_pos_blackboard", blocks_pos_blackboard);
blackboard.Set<std::string>("CollectCropsAndReplant.item_name", item_name);
std::shared_ptr<World> world = client.GetWorld();
const std::vector<Position>& positions = blackboard.Get<std::vector<Position>>(blocks_pos_blackboard);
for (int i = 0; i < positions.size(); ++i)
{
const Blockstate* block = world->GetBlock(positions[i]);
const bool ready_to_harvest = block != nullptr && !block->IsAir() && block->GetVariableValue("age") == "7";
if (ready_to_harvest)
{
if (Dig(client, positions[i], true) == Status::Failure)
{
LOG_WARNING("Error trying to break a crop");
return Status::Failure;
}
for (int i = 0; i < 50; ++i)
{
client.Yield();
}
}
}
std::shared_ptr<EntityManager> entity_manager = client.GetEntityManager();
std::map<int, Vector3<double>> entity_positions;
bool moving_items = true;
while (moving_items)
{
moving_items = false;
entity_positions.clear();
{
auto entities = entity_manager->GetEntities();
for (const auto& [id, entity] : *entities)
{
if (entity->GetType() == EntityType::ItemEntity)
{
std::shared_ptr<ItemEntity> item = std::static_pointer_cast<ItemEntity>(entity);
#if PROTOCOL_VERSION < 340 /* < 1.12.2 */
if (AssetsManager::getInstance().Items().at(item->GetDataItem().GetBlockID()).at(item->GetDataItem().GetItemDamage())->GetName() == item_name)
#else
if (AssetsManager::getInstance().Items().at(item->GetDataItem().GetItemID())->GetName() == item_name)
#endif
{
entity_positions[id] = entity->GetPosition();
moving_items = entity->GetSpeed().SqrNorm() > 0.0001;
if (moving_items)
{
break;
}
}
}
}
}
client.Yield();
}
for (const auto& e : entity_positions)
{
if (GoTo(client, e.second, 2) == Status::Failure)
{
LOG_WARNING("Error trying to pick up a crop item (can't get close enough to " << e.second << ")");
return Status::Failure;
}
auto start = std::chrono::steady_clock::now();
while (true)
{
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 5000)
{
LOG_WARNING("Error waiting for crop item pick-up (Timeout).");
return Status::Failure;
}
{
if (entity_manager->GetEntity(e.first) == nullptr)
{
break;
}
}
client.Yield();
}
}
for (int i = positions.size() - 1; i >= 0; --i)
{
const Blockstate* block = world->GetBlock(positions[i]);
const bool to_replant = block != nullptr && block->IsAir();
if (to_replant)
{
if (PlaceBlock(client, item_name, positions[i]) == Status::Failure)
{
LOG_WARNING("Error trying to replant a crop");
return Status::Failure;
}
for (int i = 0; i < 50; ++i)
{
client.Yield();
}
}
}
return Status::Success;
}
Status DestroyItems(BehaviourClient& client, const std::string& item_name)
{
Blackboard& blackboard = client.GetBlackboard();
// Set input parameters for debugger
blackboard.Set<std::string>("DestroyItems.item_name", item_name);
std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
std::shared_ptr<Window> inventory = inventory_manager->GetPlayerInventory();
const Position& cactus_standing_pos = blackboard.Get<Position>("DispenserFarmBot.cactus_standing_position");
const Position& cactus_pos = blackboard.Get<Position>("DispenserFarmBot.cactus_position");
std::vector<short> slots_to_remove;
for (const auto& [idx, slot] : inventory->GetSlots())
{
if (idx >= inventory->GetFirstPlayerInventorySlot() && !slot.IsEmptySlot() &&
#if PROTOCOL_VERSION < 340 /* < 1.12.2 */
AssetsManager::getInstance().Items().at(slot.GetBlockID()).at(slot.GetItemDamage())->GetName() == item_name
#else
AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName() == item_name
#endif
)
{
slots_to_remove.push_back(idx);
}
}
if (slots_to_remove.size() == 0)
{
return Status::Success;
}
if (GoTo(client, cactus_standing_pos, 0, 0, 0) == Status::Failure)
{
LOG_WARNING("Error trying to go to the cactus standing position");
return Status::Failure;
}
LookAt(client, Vector3<double>(0.5) + cactus_pos, false);
for (int i = 0; i < slots_to_remove.size(); ++i)
{
if (DropItemsFromContainer(client, Window::PLAYER_INVENTORY_INDEX, slots_to_remove[i]) == Status::Failure)
{
LOG_WARNING("Error trying to drop the items on the cactus");
return Status::Failure;
}
}
return Status::Success;
}
@@ -0,0 +1,494 @@
#include <iostream>
#include <string>
#include "botcraft/AI/SimpleBehaviourClient.hpp"
#include "botcraft/AI/Tasks/AllTasks.hpp"
#include "botcraft/Utilities/Logger.hpp"
#include "botcraft/Utilities/StdAnyUtilities.hpp"
#include "DispenserFarmTasks.hpp"
void ShowHelp(const char* argv0)
{
std::cout << "Usage: " << argv0 << " <options>\n"
<< "Options:\n"
<< "\t-h, --help\tShow this help message\n"
<< "\t--address\tAddress of the server you want to connect to, default: 127.0.0.1:25565\n"
<< "\t--login\t\tPlayer name in offline mode, empty for Microsoft account, default: BCDispenserGuy\n"
<< std::endl;
}
void RegisterBlackboardTypesToDebugger();
struct Args
{
bool help = false;
std::string address = "127.0.0.1:25565";
std::string login = "BCDispenserGuy";
int return_code = 0;
};
Args ParseCommandLine(int argc, char* argv[]);
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateTree();
int main(int argc, char* argv[])
{
try
{
// Init logging, log everything >= Info, only to console, no file
Botcraft::Logger::GetInstance().SetLogLevel(Botcraft::LogLevel::Info);
Botcraft::Logger::GetInstance().SetFilename("");
// Add a name to this thread for logging
Botcraft::Logger::GetInstance().RegisterThread("main");
// Add some types to blackboard debugger
RegisterBlackboardTypesToDebugger();
Args args;
if (argc == 1)
{
LOG_WARNING("No command arguments. Using default options.");
ShowHelp(argv[0]);
}
else
{
args = ParseCommandLine(argc, argv);
if (args.help)
{
ShowHelp(argv[0]);
return 0;
}
if (args.return_code != 0)
{
return args.return_code;
}
}
auto dispenser_farm_tree = CreateTree();
Botcraft::SimpleBehaviourClient client(true);
client.SetAutoRespawn(true);
LOG_INFO("Starting connection process");
client.Connect(args.address, args.login);
// Wait 10 seconds then start executing the tree
Botcraft::Utilities::SleepFor(std::chrono::seconds(10));
client.SetBehaviourTree(dispenser_farm_tree);
client.RunBehaviourUntilClosed();
client.Disconnect();
return 0;
}
catch (std::exception& e)
{
LOG_FATAL("Exception: " << e.what());
return 1;
}
catch (...)
{
LOG_FATAL("Unknown exception");
return 2;
}
}
void RegisterBlackboardTypesToDebugger()
{
// Register a custom parser for small vectors of int (to print them in the blackboard debugger)
Botcraft::Utilities::AnyParser::RegisterType<std::vector<int>>([](const std::any& f) {
const std::vector<int>& v = std::any_cast<const std::vector<int>&>(f);
if (v.size() > 10)
{
return Botcraft::Utilities::AnyParser::DefaultToString(f);
}
std::stringstream s;
s << '[';
for (size_t i = 0; i < v.size(); ++i)
{
s << v[i] << (i == v.size() - 1 ? ']' : ',');
}
return s.str();
});
// Register a custom parser for small vectors of Position (to print them in the blackboard debugger)
Botcraft::Utilities::AnyParser::RegisterType(std::type_index(typeid(std::vector<Botcraft::Position>)), [](const std::any& f) {
const std::vector<Botcraft::Position>& v = std::any_cast<const std::vector<Botcraft::Position>&>(f);
if (v.size() > 10)
{
return "Vector of " + std::to_string(v.size()) + " block Positions";
}
std::stringstream s;
s << "[\n";
for (size_t i = 0; i < v.size(); ++i)
{
s << "\t" << v[i] << (i == v.size() - 1 ? "\n]" : ",\n");
}
return s.str();
});
}
Args ParseCommandLine(int argc, char* argv[])
{
Args args;
for (int i = 1; i < argc; ++i)
{
std::string arg = argv[i];
if (arg == "-h" || arg == "--help")
{
ShowHelp(argv[0]);
args.help = true;
return args;
}
else if (arg == "--address")
{
if (i + 1 < argc)
{
args.address = argv[++i];
}
else
{
LOG_FATAL("--address requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--login")
{
if (i + 1 < argc)
{
args.login = argv[++i];
}
else
{
LOG_FATAL("--login requires an argument");
args.return_code = 1;
return args;
}
}
}
return args;
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateBuyTree(const std::string& item_name, const std::string& blackboard_entity_location)
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("buy " + item_name + " tree")
// Buy an item if we don't have one already
.selector()
.leaf(Botcraft::HasItemInInventory, item_name, 1)
.sequence()
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.buying_standing_position", "GoTo.goal")
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.dist_tolerance", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist_xz", 0)
.leaf("go to buying position", Botcraft::GoToBlackboard)
.leaf(CopyRandomFromVectorBlackboardData<int>, blackboard_entity_location, "InteractEntity.entity_id")
.leaf(Botcraft::SetBlackboardData<bool>, "InteractEntity.swing", true)
.leaf("open trading interface", Botcraft::InteractEntityBlackboard)
.repeater(100).leaf(Botcraft::Yield)
.selector()
// If trading fail, we still want to close the container
.leaf("buy item", Botcraft::TradeName, item_name, true, -1)
.leaf([](Botcraft::SimpleBehaviourClient& c) { Botcraft::CloseContainer(c, -1); return Botcraft::Status::Failure;})
.end()
.leaf("close trading interface", Botcraft::CloseContainer, -1)
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateRottenFleshEmeraldTree()
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("collect and sell rotten flesh")
.sequence()
.selector()
// If we don't have rotten flesh, take some in the chest
.leaf(Botcraft::HasItemInInventory, "minecraft:rotten_flesh", 64)
.sequence()
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.buying_standing_position", "GoTo.goal")
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.dist_tolerance", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist_xz", 0)
.leaf(Botcraft::GoToBlackboard)
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.rotten_flesh_shulker_position", "OpenContainer.pos")
.leaf(Botcraft::OpenContainerBlackboard)
.repeater(100).leaf(Botcraft::Yield)
.succeeder().leaf("take rotten flesh", TakeFromChest, "minecraft:rotten_flesh", 64)
.leaf("close rotten flesh container", Botcraft::CloseContainer, -1)
.end()
.end()
// Trade some rotten flesh
.repeater(0).inverter().sequence()
.leaf(Botcraft::HasItemInInventory, "minecraft:rotten_flesh", 32)
.leaf(CopyRandomFromVectorBlackboardData<int>, "DispenserFarmBot.cleric_id", "InteractEntity.entity_id")
.leaf(Botcraft::SetBlackboardData<bool>, "InteractEntity.swing", true)
.leaf("open trading interface", Botcraft::InteractEntityBlackboard)
.selector()
// If trading fail, we still want to close the container
.leaf("sell rotten flesh", Botcraft::TradeName, "minecraft:rotten_flesh", false, -1)
.leaf([](Botcraft::SimpleBehaviourClient& c) { Botcraft::CloseContainer(c, -1); return Botcraft::Status::Failure;})
.end()
.leaf("close trading interface", Botcraft::CloseContainer, -1)
.repeater(50).leaf(Botcraft::Yield)
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateBonesEmeraldTree()
{
std::array<std::array<std::string, 3>, 3> recipe_bone_meal, recipe_white_dye;
recipe_bone_meal[0][0] = "minecraft:bone";
recipe_white_dye[0][0] = "minecraft:bone_meal";
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("collect and sell bones")
.sequence()
.selector()
// If we don't have white dye, take some bones in the chest
.leaf(Botcraft::HasItemInInventory, "minecraft:white_dye", 64)
.sequence()
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.buying_standing_position", "GoTo.goal")
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.dist_tolerance", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist_xz", 0)
.leaf(Botcraft::GoToBlackboard)
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.bones_shulker_position", "OpenContainer.pos")
.leaf("open bones container", Botcraft::OpenContainerBlackboard)
.repeater(100).leaf(Botcraft::Yield)
.succeeder().leaf("take bones", TakeFromChest, "minecraft:bone", 32)
.leaf("close bones container", Botcraft::CloseContainer, -1)
.end()
.end()
// While we have at least one bone
// Craft as many bone meal as possible
.repeater(0).inverter().sequence()
.leaf(Botcraft::HasItemInInventory, "minecraft:bone", 1)
.leaf("craft bone meal", Botcraft::CraftNamed, recipe_bone_meal, true)
.end()
// While we have at least one bone meal
// Craft as many white dye as possible
.repeater(0).inverter().sequence()
.leaf(Botcraft::HasItemInInventory, "minecraft:bone_meal", 1)
.leaf("craft white dye", Botcraft::CraftNamed, recipe_white_dye, true)
.end()
// While we have some white dye, sell them
.repeater(0).inverter().sequence()
.leaf(Botcraft::HasItemInInventory, "minecraft:white_dye", 12)
.leaf(CopyRandomFromVectorBlackboardData<int>, "DispenserFarmBot.shepperd_id", "InteractEntity.entity_id")
.leaf(Botcraft::SetBlackboardData<bool>, "InteractEntity.swing", true)
.leaf("open trading interface", Botcraft::InteractEntityBlackboard)
.selector()
// If trading fail, we still want to close the container
.leaf("sell white dye", Botcraft::TradeName, "minecraft:white_dye", false, -1)
.leaf([](Botcraft::SimpleBehaviourClient& c) { Botcraft::CloseContainer(c, -1); return Botcraft::Status::Failure;})
.end()
.leaf("close trading interface", Botcraft::CloseContainer, -1)
.repeater(50).leaf(Botcraft::Yield)
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateCropEmeraldTree(const std::string& item_name, const std::string& blackboard_crops_location)
{
int min_item_to_sell = item_name == "minecraft:carrot" ? 22 : 26;
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("collect and sell " + item_name)
.sequence()
// Collect crops if we don't have some
.selector()
.leaf(Botcraft::HasItemInInventory, item_name, 64)
.leaf("collect crops and replant", CollectCropsAndReplant, blackboard_crops_location, item_name)
.end()
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.buying_standing_position", "GoTo.goal")
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.dist_tolerance", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist_xz", 0)
.leaf("go to selling position", Botcraft::GoToBlackboard)
// Sell crops
.repeater(0).inverter().sequence()
.leaf("check if enough crop to sell", Botcraft::HasItemInInventory, item_name, min_item_to_sell)
.leaf(CopyRandomFromVectorBlackboardData<int>, "DispenserFarmBot.farmer_id", "InteractEntity.entity_id")
.leaf(Botcraft::SetBlackboardData<bool>, "InteractEntity.swing", true)
.leaf("interact with villager", Botcraft::InteractEntityBlackboard)
.repeater(100).leaf(Botcraft::Yield)
.selector()
// If trading fail, we still want to close the container
.leaf("trade", Botcraft::TradeName, item_name, false, -1)
.leaf([](Botcraft::SimpleBehaviourClient& c) { Botcraft::CloseContainer(c, -1); return Botcraft::Status::Failure;})
.end()
.leaf("close trading interface", Botcraft::CloseContainer, -1)
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateEatTree()
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("eat")
.selector()
// If hungry
.inverter().leaf("check is hungry", Botcraft::IsHungry, 20)
// Go buy some food, then eat
.sequence()
.leaf("check if has 3 emeralds in inventory", Botcraft::HasItemInInventory, "minecraft:emerald", 3)
.tree(CreateBuyTree("minecraft:golden_carrot", "DispenserFarmBot.farmer_id"))
.leaf("eat", Botcraft::Eat, "minecraft:golden_carrot", true)
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateCollectCobblestoneTree()
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("collect cobblestone")
.selector()
// If we already have engouh, don't enter the loop
.leaf("check if >=7 cobblestone in inventory", Botcraft::HasItemInInventory, "minecraft:cobblestone", 7)
// Repeat 10 times (7 + 3 in case something goes wrong)
.repeater(10).selector()
// If already 7 in inventory, no need to get more
.leaf("check if >=7 cobblestone in inventory", Botcraft::HasItemInInventory, "minecraft:cobblestone", 7)
.sequence()
// Get a pickaxe if don't have one already
.selector()
.leaf("set pickaxe in hand", Botcraft::SetItemInHand, "minecraft:stone_pickaxe", Botcraft::Hand::Right)
.sequence()
.leaf("check if has 1 emerald in inventory", Botcraft::HasItemInInventory, "minecraft:emerald", 1)
.tree(CreateBuyTree("minecraft:stone_pickaxe", "DispenserFarmBot.toolsmith_id"))
.leaf("set pick in hand", Botcraft::SetItemInHand, "minecraft:stone_pickaxe", Botcraft::Hand::Right)
.end()
.end()
// Go to the mining position
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.stone_standing_position", "GoTo.goal")
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.dist_tolerance", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist_xz", 0)
.leaf("go to mining position", Botcraft::GoToBlackboard)
// Get cobblestone
.leaf("mine cobblestone", MineCobblestone)
.end()
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateCraftDispenserTree()
{
std::array<std::array<std::string, 3>, 3> dispenser_recipe;
dispenser_recipe[0][0] = "minecraft:cobblestone";
dispenser_recipe[0][1] = "minecraft:cobblestone";
dispenser_recipe[0][2] = "minecraft:cobblestone";
dispenser_recipe[1][0] = "minecraft:cobblestone";
dispenser_recipe[1][1] = "minecraft:bow";
dispenser_recipe[1][2] = "minecraft:cobblestone";
dispenser_recipe[2][0] = "minecraft:cobblestone";
dispenser_recipe[2][1] = "minecraft:redstone";
dispenser_recipe[2][2] = "minecraft:cobblestone";
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("craft dispenser")
.sequence()
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.buying_standing_position", "GoTo.goal")
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.dist_tolerance", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist_xz", 0)
.leaf("go to crafting table", Botcraft::GoToBlackboard)
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.crafting_table_position", "OpenContainer.pos")
.leaf("open crafting table interface", Botcraft::OpenContainerBlackboard)
.repeater(100).leaf(Botcraft::Yield)
.leaf("craft dispenser", Botcraft::CraftNamed, dispenser_recipe, false)
.leaf("close container", Botcraft::CloseContainer, -1)
.selector()
.leaf("store dispenser", StoreDispenser)
// Stop all behaviour if output chest is potentially full
.sequence()
.leaf(Botcraft::Say, "Error trying to put dispenser in output chest, stopping behaviour...")
.leaf([](Botcraft::SimpleBehaviourClient& c) { c.SetBehaviourTree(nullptr); return Botcraft::Status::Success; })
.leaf(Botcraft::Yield)
.end()
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateCleanStorageTree()
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("clean storage")
.sequence()
.leaf("clean bones chest", CleanChest, "DispenserFarmBot.bones_shulker_position", "minecraft:bone")
.leaf("clean rotten flesh chest", CleanChest, "DispenserFarmBot.rotten_flesh_shulker_position", "minecraft:rotten_flesh")
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateDespawnFrostWalkerTree()
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("despawn frost walker mobs")
.selector()
.inverter().leaf("check for frost walker mobs", CheckFrostWalkerMob)
.sequence()
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.despawn_position", "GoTo.goal")
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.dist_tolerance", 1)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist_xz", 0)
.leaf("go to despawn position", Botcraft::GoToBlackboard)
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateSleepTree()
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("sleep")
.selector()
// If it's night
.inverter().leaf("check if night", Botcraft::IsNightTime)
.sequence()
// Go to the bed
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.bed_position", "GoTo.goal")
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.dist_tolerance", 2)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist", 0)
.leaf(Botcraft::SetBlackboardData<int>, "GoTo.min_end_dist_xz", 0)
.leaf("go to bed", Botcraft::GoToBlackboard)
// Right click the bed every second until it's day time
.repeater(0).sequence()
.leaf(Botcraft::CopyBlackboardData, "DispenserFarmBot.bed_position", "InteractWithBlock.pos")
.leaf(Botcraft::SetBlackboardData<bool>, "InteractWithBlock.animation", true)
.leaf("interact with bed", Botcraft::InteractWithBlockBlackboard)
// Wait ~1s
.repeater(100).leaf(Botcraft::Yield)
.inverter().leaf("check if night", Botcraft::IsNightTime)
.end()
.end()
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateCollectEmeraldsTree()
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("collect emeralds")
.sequence()
.succeeder().tree(CreateBonesEmeraldTree())
.succeeder().tree(CreateRottenFleshEmeraldTree())
.succeeder().tree(CreateCropEmeraldTree("minecraft:potato", "DispenserFarmBot.potato_positions"))
.succeeder().tree(CreateCropEmeraldTree("minecraft:carrot", "DispenserFarmBot.carrot_positions"))
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CreateTree()
{
return Botcraft::Builder<Botcraft::SimpleBehaviourClient>("main")
.sequence()
.selector()
.leaf("check if initialized in blackboard", Botcraft::CheckBlackboardBoolData, "DispenserFarmBot.initialized")
.sequence()
.leaf("initialize blocks", InitializeBlocks, 50)
.leaf("start spawners", StartSpawners)
.end()
.end()
.tree(CreateCollectEmeraldsTree())
.tree(CreateCleanStorageTree())
.leaf("sort inventory", Botcraft::SortInventory)
.leaf("destroy poisonous potatoes", DestroyItems, "minecraft:poisonous_potato")
.tree(CreateDespawnFrostWalkerTree())
.tree(CreateSleepTree())
.tree(CreateEatTree())
.leaf("check 2 emeralds in inventory", Botcraft::HasItemInInventory, "minecraft:emerald", 2)
.tree(CreateBuyTree("minecraft:bow", "DispenserFarmBot.fletcher_id"))
.leaf("check 1 emerald in inventory", Botcraft::HasItemInInventory, "minecraft:emerald", 1)
.tree(CreateBuyTree("minecraft:redstone", "DispenserFarmBot.cleric_id"))
.tree(CreateCollectCobblestoneTree())
.tree(CreateCraftDispenserTree())
.end();
}