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,10 @@
project(0_HelloWorld)
set(${PROJECT_NAME}_SOURCE_FILES
${PROJECT_SOURCE_DIR}/src/main.cpp
)
set(${PROJECT_NAME}_INCLUDE_FOLDERS
)
add_example("${${PROJECT_NAME}_INCLUDE_FOLDERS}" "${${PROJECT_NAME}_SOURCE_FILES}")
+126
View File
@@ -0,0 +1,126 @@
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include "botcraft/Game/ConnectionClient.hpp"
#include "botcraft/Utilities/Logger.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: BCHelloWorld\n"
<< std::endl;
}
struct Args
{
bool help = false;
std::string address = "127.0.0.1:25565";
std::string login = "BCHelloWorld";
int return_code = 0;
};
Args ParseCommandLine(int argc, char* argv[]);
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");
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;
}
}
Botcraft::ConnectionClient client;
LOG_INFO("Starting connection process");
client.Connect(args.address, args.login);
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
client.SendChatMessage(u8"Hello, World!");
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
client.Disconnect();
return 0;
}
catch (std::exception& e)
{
LOG_FATAL("Exception: " << e.what());
return 1;
}
catch (...)
{
LOG_FATAL("Unknown exception");
return 2;
}
return 0;
}
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")
{
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;
}
@@ -0,0 +1,13 @@
project(1_UserControlledExample)
set(${PROJECT_NAME}_SOURCE_FILES
${PROJECT_SOURCE_DIR}/include/UserControlledClient.hpp
${PROJECT_SOURCE_DIR}/src/UserControlledClient.cpp
${PROJECT_SOURCE_DIR}/src/main.cpp
)
set(${PROJECT_NAME}_INCLUDE_FOLDERS
${PROJECT_SOURCE_DIR}/include
)
add_example("${${PROJECT_NAME}_INCLUDE_FOLDERS}" "${${PROJECT_NAME}_SOURCE_FILES}")
@@ -0,0 +1,28 @@
#pragma once
#include "botcraft/Game/ManagersClient.hpp"
#include "botcraft/Renderer/RenderingManager.hpp"
class UserControlledClient : public Botcraft::ManagersClient
{
public:
UserControlledClient(bool online, bool use_renderer_);
~UserControlledClient();
protected:
#ifdef USE_GUI
void MouseCallback(const double& xoffset, const double& yoffset);
void KeyBoardCallback(const std::array<bool, static_cast<int>(Botcraft::Renderer::KEY_CODE::NUMBER_OF_KEYS)>& is_key_pressed, const double& delta_time);
#endif
virtual void Handle(ProtocolCraft::ClientboundGameProfilePacket& msg) override;
void CreateTestWorld();
protected:
#if USE_GUI
float mouse_sensitivity;
#endif
};
@@ -0,0 +1,359 @@
#include "botcraft/Game/AssetsManager.hpp"
#include "botcraft/Version.hpp"
#include "botcraft/Game/Inventory/InventoryManager.hpp"
#include "botcraft/Game/Entities/EntityManager.hpp"
#include "botcraft/Game/Entities/entities/Entity.hpp"
#include "botcraft/Game/Entities/LocalPlayer.hpp"
#include "botcraft/Game/World/World.hpp"
#include "botcraft/Game/World/Chunk.hpp"
#include "botcraft/Game/Physics/PhysicsManager.hpp"
#include "botcraft/Network/NetworkManager.hpp"
#include "botcraft/Utilities/Logger.hpp"
#include "protocolCraft/enums.hpp"
#if USE_GUI
#include "botcraft/Renderer/RenderingManager.hpp"
#endif
#include "UserControlledClient.hpp"
#include <iostream>
using namespace Botcraft;
using namespace ProtocolCraft;
UserControlledClient::UserControlledClient(bool online, bool use_renderer_) : ManagersClient(use_renderer_)
{
#if USE_GUI
mouse_sensitivity = 0.1f;
#endif
if (!online)
{
network_manager = std::make_shared<NetworkManager>(ConnectionState::Play);
world = std::make_shared<World>(false);
entity_manager = std::make_shared<EntityManager>();
inventory_manager = std::make_shared<InventoryManager>();
should_be_closed = false;
#ifdef USE_GUI
if (use_renderer)
{
rendering_manager = std::make_shared<Renderer::RenderingManager>(world, inventory_manager, entity_manager, 800, 600, CHUNK_WIDTH, false);
mouse_sensitivity = 0.1f;
rendering_manager->SetMouseCallback(std::bind(&UserControlledClient::MouseCallback, this, std::placeholders::_1, std::placeholders::_2));
rendering_manager->SetKeyboardCallback(std::bind(&UserControlledClient::KeyBoardCallback, this, std::placeholders::_1, std::placeholders::_2));
}
physics_manager = std::make_shared<PhysicsManager>(rendering_manager, inventory_manager, entity_manager, network_manager, world);
#else
physics_manager = std::make_shared<PhysicsManager>(inventory_manager, entity_manager, network_manager, world);
#endif
// Launch the thread for the position
entity_manager->GetLocalPlayer()->SetPosition(Vector3<double>(0.0, 0.0, 0.0));
physics_manager->StartPhysics();
LOG_INFO("Client created!");
LOG_INFO("Creating world...");
CreateTestWorld();
LOG_INFO("Done!");
}
}
UserControlledClient::~UserControlledClient()
{
}
void UserControlledClient::CreateTestWorld()
{
#if PROTOCOL_VERSION < 719 /* < 1.16 */
const Dimension dimension = Dimension::Overworld;
#else
const std::string dimension = "minecraft:overworld";
#endif
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
world->SetDimensionMinY(dimension, 0);
world->SetDimensionHeight(dimension, 256);
world->SetCurrentDimension(dimension);
#endif
std::shared_ptr<Entity> entity = Entity::CreateEntity(EntityType::Zombie);
entity->SetEntityID(42);
entity->SetPosition(Vector3<double>(0.0, 1.0, 0.0));
entity->SetYaw(45.0f);
entity_manager->AddEntity(entity);
#ifdef USE_GUI
if (use_renderer)
{
rendering_manager->AddEntityToUpdate(entity->GetEntityID());
}
#endif
int max_id = 0;
int min_id = std::numeric_limits<int>::max();
for (auto it = AssetsManager::getInstance().Blockstates().begin(); it != AssetsManager::getInstance().Blockstates().end(); ++it)
{
if (it->first > max_id)
{
max_id = it->first;
}
if (it->first < min_id)
{
min_id = it->first;
}
}
#if PROTOCOL_VERSION < 347 /* < 1.13 */
for (int i = 0; i < (max_id - min_id) / 16 + 1; ++i)
#else
for (int i = 0; i < (max_id - min_id) / 16 / 16 + 1; ++i)
#endif
{
world->LoadChunk(i, 0, dimension);
world->LoadChunk(i, 1, dimension);
world->LoadChunk(i, -1, dimension);
world->LoadChunk(i, -2, dimension);
}
int x = 0;
#if PROTOCOL_VERSION < 347 /* < 1.13 */
for (auto it = AssetsManager::getInstance().Blockstates().begin(); it != AssetsManager::getInstance().Blockstates().end(); ++it)
{
int id = it->first;
for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2)
{
const int z = it2->first;
Position pos;
if (x % 2 == 0)
{
pos = Position(x, 0, 2 * z);
}
else
{
pos = Position(x, 0, -2 * z - 1);
}
world->SetBlock(pos, { id, z });
world->SetBiome(pos.x, pos.z, 0);
}
x++;
}
#else
int z = 0;
for (auto it = AssetsManager::getInstance().Blockstates().begin(); it != AssetsManager::getInstance().Blockstates().end(); ++it)
{
int id = it->first;
Position pos;
if (x % 4 == 0)
{
pos = Position(x, 0, 2 * z + 1);
}
else if (x % 4 == 1)
{
pos = Position(x + 1, 0, 30 - 2 * z + 1);
}
else if (x % 4 == 2)
{
pos = Position(x, 0, -2 * z - 1);
}
else
{
pos = Position(x + 1, 0, -30 + 2 * z - 1);
}
world->SetBlock(pos, id);
z++;
if (z == 16)
{
z = 0;
x++;
}
}
#endif
#ifdef USE_GUI
if (use_renderer)
{
for (int i = 0; i < static_cast<int>(floor(x / CHUNK_WIDTH)) + 1; ++i)
{
rendering_manager->AddChunkToUpdate(i, 0);
rendering_manager->AddChunkToUpdate(i, 1);
rendering_manager->AddChunkToUpdate(i, -1);
rendering_manager->AddChunkToUpdate(i, -2);
}
}
#endif
int num_biomes = 0;
for (int i = 0; i < 256; ++i)
{
const Biome* biome = AssetsManager::getInstance().GetBiome(i);
if (biome)
{
num_biomes++;
}
}
#if PROTOCOL_VERSION < 552 /* < 1.15 */
const int biome_spacing = 1;
#else
const int biome_spacing = 4;
#endif
for (int i = 0; i < (num_biomes * biome_spacing) / 16 + 1; ++i)
{
world->LoadChunk(-i - 1, 0, dimension);
}
x = 0;
for (int i = 0; i < 256; ++i)
{
const Biome* biome = AssetsManager::getInstance().GetBiome(i);
if (biome)
{
Position pos(-(x * biome_spacing) - 1, 0, 0);
#if PROTOCOL_VERSION < 347 /* < 1.13 */
world->SetBlock(pos, { 2, 0 });
#else
world->SetBlock(pos, 9);
#endif
#if PROTOCOL_VERSION < 552 /* < 1.15 */
world->SetBiome(pos.x, pos.z, i);
#else
world->SetBiome(pos.x, pos.y, pos.z, i);
#endif
pos = Position(-(x * biome_spacing) - 1, 0, 1);
#if PROTOCOL_VERSION < 347 /* < 1.13 */
world->SetBlock(pos, { 18, 0 });
#else
world->SetBlock(pos, 157);
#endif
#if PROTOCOL_VERSION < 552 /* < 1.15 */
world->SetBiome(pos.x, pos.z, i);
#else
world->SetBiome(pos.x, pos.y, pos.z, i);
#endif
pos = Position(-(x * biome_spacing) - 1, 0, 2);
#if PROTOCOL_VERSION < 347 /* < 1.13 */
world->SetBlock(pos, { 9, 0 });
#else
world->SetBlock(pos, 34);
#endif
#if PROTOCOL_VERSION < 552 /* < 1.15 */
world->SetBiome(pos.x, pos.z, i);
#else
world->SetBiome(pos.x, pos.y, pos.z, i);
#endif
x++;
}
}
#ifdef USE_GUI
if (use_renderer)
{
for (int i = 0; i < (num_biomes * biome_spacing) / 16 + 1; ++i)
{
rendering_manager->AddChunkToUpdate(-i - 1, 0);
}
}
#endif
entity_manager->GetLocalPlayer()->SetPosition(Vector3<double>(0.5, 1.0, 0.5));
}
#ifdef USE_GUI
void UserControlledClient::MouseCallback(const double& xoffset, const double& yoffset)
{
std::shared_ptr<LocalPlayer> local_player = entity_manager->GetLocalPlayer();
float pitch = static_cast<float>(local_player->GetPitch() - yoffset * mouse_sensitivity);
if (pitch > 89.0f)
{
pitch = 89.0f;
}
else if (pitch < -89.0f)
{
pitch = -89.0f;
}
local_player->SetPitch(pitch);
float yaw = static_cast<float>(local_player->GetYaw() + xoffset * mouse_sensitivity);
while (yaw > 360.0f)
{
yaw -= 360.0f;
}
while (yaw < 0.0f)
{
yaw += 360.0f;
}
local_player->SetYaw(yaw);
}
void UserControlledClient::KeyBoardCallback(const std::array<bool, static_cast<int>(Renderer::KEY_CODE::NUMBER_OF_KEYS)>& is_key_pressed, const double& delta_time)
{
std::shared_ptr<LocalPlayer> local_player = entity_manager->GetLocalPlayer();
if (is_key_pressed[static_cast<int>(Renderer::KEY_CODE::ESC)])
{
should_be_closed = true;
}
if (is_key_pressed[static_cast<int>(Renderer::KEY_CODE::SPACE)])
{
local_player->SetInputsJump(true);
}
if (is_key_pressed[static_cast<int>(Renderer::KEY_CODE::CTRL)])
{
local_player->SetInputsSneak(true);
}
if (is_key_pressed[static_cast<int>(Renderer::KEY_CODE::SHIFT)])
{
local_player->SetInputsSprint(true);
}
if (is_key_pressed[static_cast<int>(Renderer::KEY_CODE::FORWARD)])
{
local_player->SetInputsForward(1.0);
}
if (is_key_pressed[static_cast<int>(Renderer::KEY_CODE::BACKWARD)])
{
local_player->SetInputsForward(-1.0);
}
if (is_key_pressed[static_cast<int>(Renderer::KEY_CODE::RIGHT)])
{
local_player->SetInputsLeft(-1.0);
}
if (is_key_pressed[static_cast<int>(Renderer::KEY_CODE::LEFT)])
{
local_player->SetInputsLeft(1.0);
}
}
#endif
void UserControlledClient::Handle(ClientboundGameProfilePacket& msg)
{
Botcraft::ManagersClient::Handle(msg);
#if USE_GUI
if (use_renderer)
{
rendering_manager->SetMouseCallback(std::bind(&UserControlledClient::MouseCallback, this, std::placeholders::_1, std::placeholders::_2));
rendering_manager->SetKeyboardCallback(std::bind(&UserControlledClient::KeyBoardCallback, this, std::placeholders::_1, std::placeholders::_2));
}
#endif
}
@@ -0,0 +1,148 @@
#include <iostream>
#include <string>
#include "botcraft/Game/World/World.hpp"
#include "botcraft/Utilities/Logger.hpp"
#include "UserControlledClient.hpp"
void ShowHelp(const char* argv0)
{
std::cout << "Usage: " << argv0 << " <options>\n"
<< "Options:\n"
<< "\t-h, --help\tShow this help message\n"
<< "\t--connect\tIf 0, create a local world for testing instead of connecting to a server, default 1\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: BCUserControl\n"
<< std::endl;
}
struct Args
{
bool help = false;
bool connect = true;
std::string address = "127.0.0.1:25565";
std::string login = "BCUserControl";
int return_code = 0;
};
Args ParseCommandLine(int argc, char* argv[]);
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");
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;
}
}
UserControlledClient client(args.connect, true);
if (args.connect)
{
client.SetAutoRespawn(true);
LOG_INFO("Starting connection process");
client.Connect(args.address, args.login);
}
while (!client.GetShouldBeClosed())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (args.connect)
{
client.Disconnect();
}
return 0;
}
catch (std::exception& e)
{
LOG_FATAL("Exception: " << e.what());
return 1;
}
catch (...)
{
LOG_FATAL("Unknown exception");
return 2;
}
return 0;
}
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")
{
args.help = true;
return args;
}
else if (arg == "--connect")
{
if (i + 1 < argc)
{
args.connect = static_cast<bool>(std::stoi(argv[++i]));
}
else
{
LOG_FATAL("--connect requires an argument");
args.return_code = 1;
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;
}
@@ -0,0 +1,13 @@
project(2_ChatCommandExample)
set(${PROJECT_NAME}_SOURCE_FILES
${PROJECT_SOURCE_DIR}/include/ChatCommandClient.hpp
${PROJECT_SOURCE_DIR}/src/ChatCommandClient.cpp
${PROJECT_SOURCE_DIR}/src/main.cpp
)
set(${PROJECT_NAME}_INCLUDE_FOLDERS
${PROJECT_SOURCE_DIR}/include
)
add_example("${${PROJECT_NAME}_INCLUDE_FOLDERS}" "${${PROJECT_NAME}_SOURCE_FILES}")
@@ -0,0 +1,32 @@
#pragma once
#include "botcraft/Game/Vector3.hpp"
#include "botcraft/AI/TemplatedBehaviourClient.hpp"
/// @brief Example of a class where we inherit
/// TemplatedBehaviourClient<T>, with this class as parameter.
/// We can then use Behaviour Trees with this class as
/// context, and do our stuff. We also override on Handle function
class ChatCommandClient : public Botcraft::TemplatedBehaviourClient<ChatCommandClient>
{
public:
ChatCommandClient(const bool use_renderer_);
~ChatCommandClient();
protected:
#if PROTOCOL_VERSION < 759 /* < 1.19 */
virtual void Handle(ProtocolCraft::ClientboundChatPacket& msg) override;
#else
virtual void Handle(ProtocolCraft::ClientboundPlayerChatPacket& msg) override;
virtual void Handle(ProtocolCraft::ClientboundSystemChatPacket& msg) override;
#endif
void ProcessChatMsg(const std::vector<std::string>& splitted_msg);
// Check for any spawnable blocks in a sphere from pos and prints
// all the positions into a file
// Use check_lighting to add a check on light block value (> 7)
// (warning: ignore top slabs and upside-down stairs,
// you should check for such blocks manually)
void CheckPerimeter(const Botcraft::Position& pos, const float radius, const bool check_lighting);
};
@@ -0,0 +1,373 @@
#include <sstream>
#include <fstream>
#include <iostream>
#include <iterator>
#include "botcraft/Game/World/World.hpp"
#include "botcraft/Game/Entities/EntityManager.hpp"
#include "botcraft/Game/Entities/LocalPlayer.hpp"
#include "botcraft/Network/NetworkManager.hpp"
#include "botcraft/AI/BehaviourTree.hpp"
#include "botcraft/AI/Tasks/AllTasks.hpp"
#include "ChatCommandClient.hpp"
using namespace Botcraft;
using namespace ProtocolCraft;
ChatCommandClient::ChatCommandClient(const bool use_renderer_) : TemplatedBehaviourClient<ChatCommandClient>(use_renderer_)
{
std::cout << "Known commands:\n";
std::cout << " Pathfinding to position:\n";
std::cout << " name goto x y z (speed_multiplier=1.0)\n";
std::cout << " Stop what you're doing:\n";
std::cout << " name stop\n";
std::cout << " Check perimeter for spawnable blocks and save spawnable positions to file:\n";
std::cout << " name check_perimeter [x y z (default = player position)] radius (default = 128) [check_lighting (default = true)]\n";
std::cout << " Disconnect:\n";
std::cout << " name die\n";
std::cout << " Place a block:\n";
std::cout << " name place_block minecraft:item x y z\n";
std::cout << " Break a block:\n";
std::cout << " name dig x y z\n";
std::cout << " Interact (right click) a block:\n";
std::cout << " name interact x y z\n";
}
ChatCommandClient::~ChatCommandClient()
{
}
#if PROTOCOL_VERSION < 759 /* < 1.19 */
void ChatCommandClient::Handle(ClientboundChatPacket& msg)
{
ManagersClient::Handle(msg);
// Split the message
std::istringstream ss{ msg.GetMessage().GetText() };
const std::vector<std::string> splitted({ std::istream_iterator<std::string>{ss}, std::istream_iterator<std::string>{} });
// Process it
ProcessChatMsg(splitted);
}
#else
void ChatCommandClient::Handle(ClientboundPlayerChatPacket& msg)
{
ManagersClient::Handle(msg);
// Split the message
#if PROTOCOL_VERSION == 759 /* 1.19 */
std::istringstream ss{ msg.GetSignedContent().GetText() };
#elif PROTOCOL_VERSION == 760 /* 1.19.1/2 */
std::istringstream ss{ msg.GetMessage_().GetSignedBody().GetContent().GetPlain() };
#else
std::istringstream ss{ msg.GetBody().GetContent() };
#endif
const std::vector<std::string> splitted({ std::istream_iterator<std::string>{ss}, std::istream_iterator<std::string>{} });
// Process it
ProcessChatMsg(splitted);
}
void ChatCommandClient::Handle(ClientboundSystemChatPacket& msg)
{
ManagersClient::Handle(msg);
// Split the message
std::istringstream ss{ msg.GetContent().GetText() };
const std::vector<std::string> splitted({ std::istream_iterator<std::string>{ss}, std::istream_iterator<std::string>{} });
// Process it
ProcessChatMsg(splitted);
}
#endif
void ChatCommandClient::ProcessChatMsg(const std::vector<std::string>& splitted_msg)
{
if (splitted_msg.size() < 2 || splitted_msg[0] != network_manager->GetMyName())
{
return;
}
if (splitted_msg[1] == "goto")
{
if (splitted_msg.size() < 5)
{
SendChatMessage("Usage: [BotName] [goto] [x] [y] [z] [speed_multiplier]");
return;
}
Position target_position;
float speed_multiplier = 1.0f;
try
{
target_position = Position(std::stoi(splitted_msg[2]), std::stoi(splitted_msg[3]), std::stoi(splitted_msg[4]));
if (splitted_msg.size() > 5)
{
speed_multiplier = std::stof(splitted_msg[5]);
}
}
catch (const std::invalid_argument&)
{
return;
}
catch (const std::out_of_range&)
{
return;
}
auto tree = Builder<ChatCommandClient>("goto tree")
.sequence()
// Perform the pathfinding in a Selector,
// so it exits as soon as one leaf
// returns success
.selector()
// The next three lines do exactly the same,
// they're only here to show the different
// possibilities to create a leaf. Note that
// only the lambda solution can use default
// parameters values
.leaf("go to lambda", [=](ChatCommandClient& c) { return GoTo(c, target_position, 0, 0, 0, true, false, speed_multiplier); })
.leaf("go to function", GoTo, target_position, 0, 0, 0, true, false, speed_multiplier)
.leaf("go to std::bind", std::bind(GoTo, std::placeholders::_1, target_position, 0, 0, 0, true, false, speed_multiplier))
// If goto fails, say something in chat
.leaf(Say, "Pathfinding failed :(")
.end()
// Switch back to empty behaviour
.leaf([](ChatCommandClient& c) { c.SetBehaviourTree(nullptr); return Status::Success; })
.end();
SetBehaviourTree(tree);
}
else if (splitted_msg[1] == "stop")
{
// Stop any running behaviour
SetBehaviourTree(nullptr);
}
else if (splitted_msg[1] == "check_perimeter")
{
float radius = 128.0f;
Position pos = Position(
static_cast<int>(std::floor(entity_manager->GetLocalPlayer()->GetPosition().x)),
static_cast<int>(std::floor(entity_manager->GetLocalPlayer()->GetPosition().y)),
static_cast<int>(std::floor(entity_manager->GetLocalPlayer()->GetPosition().z))
);
bool check_lighting = true;
if (splitted_msg.size() == 3)
{
radius = std::stof(splitted_msg[2]);
}
else if (splitted_msg.size() == 4)
{
radius = std::stof(splitted_msg[2]);
check_lighting = std::stoi(splitted_msg[3]);
}
else if (splitted_msg.size() == 6)
{
pos = Position(std::stoi(splitted_msg[2]), std::stoi(splitted_msg[3]), std::stoi(splitted_msg[4]));
radius = std::stof(splitted_msg[5]);
}
else if (splitted_msg.size() == 7)
{
pos = Position(std::stoi(splitted_msg[2]), std::stoi(splitted_msg[3]), std::stoi(splitted_msg[4]));
radius = std::stof(splitted_msg[5]);
check_lighting = std::stoi(splitted_msg[6]);
}
CheckPerimeter(pos, radius, check_lighting);
}
else if (splitted_msg[1] == "die")
{
should_be_closed = true;
}
else if (splitted_msg[1] == "place_block")
{
if (splitted_msg.size() < 6)
{
SendChatMessage("Usage: [BotName] [place_block] [item] [x] [y] [z]");
return;
}
const std::string& item = splitted_msg[2];
Position pos;
try
{
pos = Position(std::stoi(splitted_msg[3]), std::stoi(splitted_msg[4]), std::stoi(splitted_msg[5]));
}
catch (const std::invalid_argument&)
{
return;
}
catch (const std::out_of_range&)
{
return;
}
LOG_INFO("Asked to place a block at " << pos << " (" << item << ")");
auto tree = Builder<ChatCommandClient>("place block")
// shortcut for composite<Sequence<ChatCommandClient>>()
.sequence()
.succeeder().leaf(PlaceBlock, item, pos, PlayerDiggingFace::Up, true, true)
// Switch back to empty behaviour
.leaf([](ChatCommandClient& c) { c.SetBehaviourTree(nullptr); return Status::Success; })
.end();
SetBehaviourTree(tree);
}
else if (splitted_msg[1] == "dig")
{
if (splitted_msg.size() < 5)
{
SendChatMessage("Usage: [BotName] [dig] [x] [y] [z]");
return;
}
Position pos;
try
{
pos = Position(std::stoi(splitted_msg[2]), std::stoi(splitted_msg[3]), std::stoi(splitted_msg[4]));
}
catch (const std::invalid_argument&)
{
return;
}
catch (const std::out_of_range&)
{
return;
}
auto tree = Builder<ChatCommandClient>("dig")
// shortcut for composite<Sequence<ChatCommandClient>>()
.sequence()
.succeeder().leaf("diggy diggy hole", Dig, pos, true, PlayerDiggingFace::Up)
// Switch back to empty behaviour
.leaf([](ChatCommandClient& c) { c.SetBehaviourTree(nullptr); return Status::Success; })
.end();
SetBehaviourTree(tree);
}
else if (splitted_msg[1] == "interact")
{
if (splitted_msg.size() < 5)
{
SendChatMessage("Usage: [BotName] [interact] [x] [y] [z]");
return;
}
Position pos;
try
{
pos = Position(std::stoi(splitted_msg[2]), std::stoi(splitted_msg[3]), std::stoi(splitted_msg[4]));
}
catch (const std::invalid_argument&)
{
return;
}
catch (const std::out_of_range&)
{
return;
}
auto tree = Builder<ChatCommandClient>("interact")
// shortcut for composite<Sequence<ChatCommandClient>>()
.sequence()
.succeeder().sequence()
.leaf("go next to block", GoTo, pos, 4, 0, 1, true, false, 1.0f)
// Set interaction position in the blackboard
.leaf(SetBlackboardData<Position>, "InteractWithBlock.pos", pos)
.selector()
// Perform action using the data in the blackboard
.leaf("interact with block", InteractWithBlockBlackboard)
// Say something if it fails
.leaf(Say, "Interacting failed :(")
.end()
// Remove interaction position in the blackboard because
// we don't want to leave a mess (and to show how to do it)
.leaf(RemoveBlackboardData, "InteractWithBlock.pos")
.end()
// Switch back to empty behaviour
.leaf([](ChatCommandClient& c) { c.SetBehaviourTree(nullptr); return Status::Success; })
.end();
SetBehaviourTree(tree);
}
else
{
return;
}
}
void ChatCommandClient::CheckPerimeter(const Position& pos, const float radius, const bool check_lighting)
{
std::vector<Position> found_positions;
Position current_position;
for (int y = static_cast<int>(-radius - 1); y < radius + 1; ++y)
{
current_position.y = pos.y + y;
for (int x = static_cast<int>(-radius - 1); x < radius + 1; ++x)
{
current_position.x = pos.x + x;
for (int z = static_cast<int>(-radius - 1); z < radius + 1; ++z)
{
current_position.z = pos.z + z;
if (x * x + y * y + z * z > radius * radius)
{
continue;
}
const Blockstate* block = world->GetBlock(current_position);
if (block == nullptr || !block->IsAir())
{
continue;
}
Position adjacent_position = current_position;
adjacent_position.y -= 1;
const Blockstate *adjacent_block = world->GetBlock(adjacent_position);
if (!adjacent_block ||
adjacent_block->IsFluid() ||
!adjacent_block->IsSolid() ||
adjacent_block->IsTransparent() ||
adjacent_block->GetName() == "minecraft:bedrock" ||
adjacent_block->GetName() == "minecraft:barrier")
{
continue;
}
adjacent_position.y += 2;
adjacent_block = world->GetBlock(adjacent_position);
if (adjacent_block &&
(adjacent_block->IsSolid() ||
adjacent_block->IsFluid()))
{
continue;
}
if (check_lighting && world->GetBlockLight(current_position) > 7)
{
continue;
}
found_positions.push_back(current_position);
}
}
}
std::ofstream output_file("perimeter_check_" + std::to_string(pos.x) + "_" + std::to_string(pos.y) + "_" + std::to_string(pos.z) + "_radius_" + std::to_string(radius) + ".txt", std::ios::out);
if (output_file.is_open())
{
for (int i = 0; i < found_positions.size(); ++i)
{
output_file << found_positions[i] << "\n";
}
output_file.close();
}
}
@@ -0,0 +1,123 @@
#include <iostream>
#include <string>
#include "botcraft/Utilities/Logger.hpp"
#include "ChatCommandClient.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: BCChatCommand\n"
<< std::endl;
}
struct Args
{
bool help = false;
std::string address = "127.0.0.1:25565";
std::string login = "BCChatCommand";
int return_code = 0;
};
Args ParseCommandLine(int argc, char* argv[]);
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");
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;
}
}
ChatCommandClient client(true);
client.SetAutoRespawn(true);
LOG_INFO("Starting connection process");
client.Connect(args.address, args.login);
client.RunBehaviourUntilClosed();
client.Disconnect();
return 0;
}
catch (std::exception& e)
{
LOG_FATAL("Exception: " << e.what());
return 1;
}
catch (...)
{
LOG_FATAL("Unknown exception");
return 2;
}
}
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;
}
@@ -0,0 +1,10 @@
project(3_SimpleAFKExample)
set(${PROJECT_NAME}_SOURCE_FILES
${PROJECT_SOURCE_DIR}/src/main.cpp
)
set(${PROJECT_NAME}_INCLUDE_FOLDERS
)
add_example("${${PROJECT_NAME}_INCLUDE_FOLDERS}" "${${PROJECT_NAME}_SOURCE_FILES}")
@@ -0,0 +1,126 @@
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include "botcraft/Game/ConnectionClient.hpp"
#include "botcraft/Utilities/Logger.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: BCAFK\n"
<< std::endl;
}
struct Args
{
bool help = false;
std::string address = "127.0.0.1:25565";
std::string login = "BCAFK";
int return_code = 0;
};
Args ParseCommandLine(int argc, char* argv[]);
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");
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;
}
}
Botcraft::ConnectionClient client;
LOG_INFO("Starting connection process");
client.Connect(args.address, args.login);
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
client.Disconnect();
return 0;
}
catch (std::exception& e)
{
LOG_FATAL("Exception: " << e.what());
return 1;
}
catch (...)
{
LOG_FATAL("Unknown exception");
return 2;
}
}
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;
}
@@ -0,0 +1,14 @@
project(4_MapCreatorExample)
set(${PROJECT_NAME}_SOURCE_FILES
${PROJECT_SOURCE_DIR}/include/MapCreationTasks.hpp
${PROJECT_SOURCE_DIR}/include/CustomBehaviourTree.hpp
${PROJECT_SOURCE_DIR}/src/MapCreationTasks.cpp
${PROJECT_SOURCE_DIR}/src/main.cpp
)
set(${PROJECT_NAME}_INCLUDE_FOLDERS
${PROJECT_SOURCE_DIR}/include
)
add_example("${${PROJECT_NAME}_INCLUDE_FOLDERS}" "${${PROJECT_NAME}_SOURCE_FILES}")
@@ -0,0 +1,31 @@
#pragma once
#include "botcraft/AI/BehaviourTree.hpp"
/// @brief A Decorator that ticks its child until
/// it successes. If ticked n times with no
/// success, return failure.
/// @tparam Context The tree context type
template<class Context>
class RepeatUntilSuccess : public Botcraft::Decorator<Context>
{
public:
RepeatUntilSuccess(const std::string& s, const size_t n_) : Botcraft::Decorator<Context>(s), n(n_) {}
protected:
virtual Botcraft::Status TickImpl(Context& context) const override
{
for (size_t i = 0; i < n; ++i)
{
Botcraft::Status child_return = this->TickChild(context);
if (child_return == Botcraft::Status::Success)
{
return Botcraft::Status::Success;
}
}
return Botcraft::Status::Failure;
}
private:
size_t n;
};
@@ -0,0 +1,60 @@
#pragma once
#include <string>
#include "botcraft/AI/BehaviourClient.hpp"
#include "botcraft/AI/Status.hpp"
#include "botcraft/Game/Vector3.hpp"
/// @brief Check all blocks nearby for chests. Save the found positions in World.ChestsPos
/// @param c The client performing the action
/// @return Always return Success
Botcraft::Status GetAllChestsAround(Botcraft::BehaviourClient& c);
/// @brief Search in nearby chests for a stack of food, put it in the inventory when found.
/// @param c The client performing the action
/// @param food_name The food item name
/// @return Success if the given food is in inventory, Failure otherwise
Botcraft::Status GetSomeFood(Botcraft::BehaviourClient& c, const std::string& food_name);
/// @brief Get list of blocks in the inventory (ignoring the offhand), store it in the blackboard at Inventory.block_list
/// @param c The client performing the action
/// @return Success if at least one block, Failure otherwise
Botcraft::Status GetBlocksAvailableInInventory(Botcraft::BehaviourClient& c);
/// @brief Take or deposit items in nearby chests
/// @param c The client performing the action
/// @param food_name Food item you don't want to take/deposit
/// @param take_from_chest If true, take items from chests, if false deposit items
/// @return Success if all the items were deposited/the inventory is full, Failure otherwise
Botcraft::Status SwapChestsInventory(Botcraft::BehaviourClient& c, const std::string& food_name, const bool take_from_chest);
/// @brief Read block list in blackboard at Inventory.block_list and fill in NextTask.action, NextTask.pos, NextTask.face and NextTask.item
/// @param c The client performing the action
/// @return success if a task was found, failure otherwise
Botcraft::Status FindNextTask(Botcraft::BehaviourClient& c);
/// @brief Read the blackboard NextTask.XXX values, and perform the given task
/// @param c The client performing the action
/// @return Success if the task was correctly executed, failure otherwise
Botcraft::Status ExecuteNextTask(Botcraft::BehaviourClient& c);
/// @brief Check if the whole structure is built, check in the blackboard for CheckCompletion.(print_details, print_errors and full_check) to know if details should be displayed in the console
/// @param c The client performing the action
/// @return Success if the structure is fully built, failure otherwise
Botcraft::Status CheckCompletion(Botcraft::BehaviourClient& c);
/// @brief Write a message in the console, prefixed with bot name
/// @param c The client performing the action
/// @param msg The string to write in the console
/// @return Always return success
Botcraft::Status WarnConsole(Botcraft::BehaviourClient& c, const std::string& msg);
/// @brief Loads a NBT file (unzipped) and store the target structure in the blackboard of the given client
/// @param c The client performing the action
/// @param path The path to the unzipped NBT file
/// @param offset Starting offset position to build the structure
/// @param temp_block Minecraft item/block name used as scaffolding block in the NBT
/// @param log_info Whether or not the loading information should be logged
/// @return Success if the file was correctly loaded, failure otherwise
Botcraft::Status LoadNBT(Botcraft::BehaviourClient& c, const std::string& path, const Botcraft::Position& offset, const std::string& temp_block, const bool log_info);
@@ -0,0 +1,924 @@
#include "MapCreationTasks.hpp"
#include "botcraft/Network/NetworkManager.hpp"
#include "botcraft/Game/AssetsManager.hpp"
#include "botcraft/Game/Entities/EntityManager.hpp"
#include "botcraft/Game/Entities/LocalPlayer.hpp"
#include "botcraft/Game/World/World.hpp"
#include "botcraft/Game/Vector3.hpp"
#include "botcraft/Game/Inventory/InventoryManager.hpp"
#include "botcraft/Game/Inventory/Window.hpp"
#include "botcraft/AI/Tasks/AllTasks.hpp"
#include "botcraft/Utilities/Logger.hpp"
#include <fstream>
#include <iostream>
#include <iterator>
#include <random>
#include <set>
#include <unordered_set>
using namespace Botcraft;
using namespace ProtocolCraft;
Status GetAllChestsAround(BehaviourClient& c)
{
std::vector<Position> chests_pos;
std::shared_ptr<LocalPlayer> local_player = c.GetLocalPlayer();
std::shared_ptr<World> world = c.GetWorld();
const Position player_position(
static_cast<int>(std::floor(local_player->GetX())),
static_cast<int>(std::floor(local_player->GetY())),
static_cast<int>(std::floor(local_player->GetZ()))
);
Position checked_position;
{
auto all_chunks = world->GetChunks();
for (auto it = all_chunks->begin(); it != all_chunks->end(); ++it)
{
for (int x = 0; x < CHUNK_WIDTH; ++x)
{
checked_position.x = x;
for (int y = 0; y < it->second.GetHeight(); ++y)
{
checked_position.y = y + it->second.GetMinY();
for (int z = 0; z < CHUNK_WIDTH; ++z)
{
checked_position.z = z;
const Blockstate* block = it->second.GetBlock(checked_position);
if (block != nullptr && block->GetName() == "minecraft:chest")
{
const Position world_position(
it->first.first * CHUNK_WIDTH + x,
y + world->GetMinY(),
it->first.second * CHUNK_WIDTH + z
);
chests_pos.push_back(world_position);
}
}
}
}
}
}
c.GetBlackboard().Set("World.ChestsPos", chests_pos);
return Status::Success;
}
Status GetSomeFood(BehaviourClient& c, const std::string& food_name)
{
std::shared_ptr<InventoryManager> inventory_manager = c.GetInventoryManager();
GetAllChestsAround(c);
const std::vector<Position>& chests = c.GetBlackboard().Get<std::vector<Position>>("World.ChestsPos");
std::vector<size_t> chests_indices(chests.size());
for (size_t i = 0; i < chests.size(); ++i)
{
chests_indices[i] = i;
}
std::mt19937 random_engine = std::mt19937(static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()));
std::shuffle(chests_indices.begin(), chests_indices.end(), random_engine);
short container_id;
bool item_taken = false;
for (size_t index = 0; index < chests.size(); ++index)
{
const size_t i = chests_indices[index];
// If we can't open this chest for a reason
if (OpenContainer(c, chests[i]) == Status::Failure)
{
continue;
}
short player_dst = -1;
while (true)
{
std::vector<short> slots_src;
container_id = inventory_manager->GetFirstOpenedWindowId();
if (container_id == -1)
{
continue;
}
const std::shared_ptr<Window> container = inventory_manager->GetWindow(container_id);
if (container == nullptr)
{
continue;
}
const short first_player_index = container->GetFirstPlayerInventorySlot();
player_dst = first_player_index + 9 * 3;
const std::map<short, Slot> slots = container->GetSlots();
slots_src.reserve(slots.size());
for (const auto& [id, slot] : slots)
{
// Chest is src
if (id >= 0
&& id < first_player_index
&& !slot.IsEmptySlot()
&& AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName() == food_name
)
{
slots_src.push_back(id);
}
}
if (slots_src.size() > 0)
{
// Select a random slot in both src and dst
const int src_index = slots_src.size() == 1 ? 0 : std::uniform_int_distribution<int>(0, static_cast<int>(slots_src.size()) - 1)(random_engine);
// Try to swap the items
if (SwapItemsInContainer(c, container_id, slots_src[src_index], player_dst) == Status::Success)
{
item_taken = true;
break;
}
}
// This means the chest doesn't have any food
else
{
break;
}
}
CloseContainer(c, container_id);
if (!item_taken)
{
continue;
}
// Wait until player inventory is updated after the container is closed
auto start = std::chrono::steady_clock::now();
while (AssetsManager::getInstance().Items().at(inventory_manager->GetPlayerInventory()->GetSlot(Window::INVENTORY_HOTBAR_START).GetItemID())->GetName() != food_name)
{
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 10000)
{
LOG_WARNING("Something went wrong trying to get food from chest (Timeout).");
return Status::Failure;
}
c.Yield();
}
// No need to continue loooking in the other chests
break;
}
return item_taken ? Status::Success : Status::Failure;
}
Status GetBlocksAvailableInInventory(BehaviourClient& c)
{
std::shared_ptr<InventoryManager> inventory_manager = c.GetInventoryManager();
std::set<std::string> blocks_in_inventory;
for (const auto& [idx, slot] : inventory_manager->GetPlayerInventory()->GetSlots())
{
if (idx >= Window::INVENTORY_STORAGE_START &&
idx < Window::INVENTORY_OFFHAND_INDEX &&
!slot.IsEmptySlot())
{
blocks_in_inventory.insert(AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName());
}
}
c.GetBlackboard().Set("Inventory.block_list", blocks_in_inventory);
return blocks_in_inventory.size() > 0 ? Status::Success : Status::Failure;
}
Status SwapChestsInventory(BehaviourClient& c, const std::string& food_name, const bool take_from_chest)
{
std::shared_ptr<InventoryManager> inventory_manager = c.GetInventoryManager();
GetAllChestsAround(c);
const std::vector<Position>& chests = c.GetBlackboard().Get<std::vector<Position>>("World.ChestsPos");
std::vector<size_t> chest_indices(chests.size());
for (size_t i = 0; i < chests.size(); ++i)
{
chest_indices[i] = i;
}
std::mt19937 random_engine(static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()));
while (true)
{
// We checked all the chests
if (chest_indices.size() == 0)
{
return Status::Success;
}
// Select a chest
size_t chest_index_index = chest_indices.size() == 1 ? 0 : std::uniform_int_distribution<int>(0, static_cast<int>(chest_indices.size()) - 1)(random_engine);
size_t chest_index = chest_indices[chest_index_index];
// If we can't open this chest for a reason
if (OpenContainer(c, chests[chest_index]) == Status::Failure)
{
continue;
}
// Find possible swaps
const short container_id = inventory_manager->GetFirstOpenedWindowId();
if (container_id == -1)
{
continue;
}
const std::shared_ptr<Window> container = inventory_manager->GetWindow(container_id);
if (container == nullptr)
{
continue;
}
const short first_player_index = (static_cast<int>(container->GetType()) + 1) * 9;
const std::map<short, Slot> slots = container->GetSlots();
std::vector<short> slots_src;
slots_src.reserve(slots.size());
std::vector<short> slots_dst;
slots_dst.reserve(slots.size());
for (const auto& [idx, slot] : slots)
{
// If take, chest is src
if (idx >= 0
&& idx < first_player_index
&& take_from_chest
&& !slot.IsEmptySlot()
&& AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName() != food_name)
{
slots_src.push_back(idx);
}
// If take, player is dst
else if (idx >= first_player_index
&& take_from_chest
&& slot.IsEmptySlot())
{
slots_dst.push_back(idx);
}
// If !take, chest is dst
else if (idx >= 0
&& idx < first_player_index
&& !take_from_chest
&& slot.IsEmptySlot())
{
slots_dst.push_back(idx);
}
// If !take, player is src
else if (idx >= first_player_index
&& !take_from_chest
&& !slot.IsEmptySlot()
&& AssetsManager::getInstance().Items().at(slot.GetItemID())->GetName() != food_name)
{
slots_src.push_back(idx);
}
}
Status swap_success = Status::Failure;
int dst_index = -1;
int src_index = -1;
if (slots_src.size() > 0 &&
slots_dst.size() > 0)
{
// Select a random slot in both src and dst
dst_index = slots_dst.size() == 1 ? 0 : std::uniform_int_distribution<int>(0, static_cast<int>(slots_dst.size()) - 1)(random_engine);
src_index = slots_src.size() == 1 ? 0 : std::uniform_int_distribution<int>(0, static_cast<int>(slots_src.size()) - 1)(random_engine);
// Try to swap the items
swap_success = SwapItemsInContainer(c, container_id, slots_src[src_index], slots_dst[dst_index]);
}
// Close the chest
CloseContainer(c, container_id);
// The chest was empty/full, remove it from the list
if ((take_from_chest && slots_src.size() == 0) ||
(!take_from_chest && slots_dst.size() == 0))
{
chest_indices.erase(chest_indices.begin() + chest_index_index);
continue;
}
// The player inventory was full/empty, end the function
else if ((take_from_chest && slots_dst.size() == 0) ||
(!take_from_chest && slots_src.size() == 0))
{
return Status::Success;
}
if (swap_success == Status::Failure)
{
continue;
}
// Wait for the confirmation from the server
auto start = std::chrono::steady_clock::now();
const short checked_slot_index = (take_from_chest ? slots_dst[dst_index] : slots_src[src_index]) - first_player_index + Window::INVENTORY_STORAGE_START;
while (true)
{
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 10000)
{
LOG_WARNING("Something went wrong trying to get items from chest (Timeout).");
return Status::Failure;
}
const Slot slot = inventory_manager->GetPlayerInventory()->GetSlot(checked_slot_index);
if ((take_from_chest && !slot.IsEmptySlot()) ||
(!take_from_chest && slot.IsEmptySlot()))
{
break;
}
c.Yield();
}
}
return Status::Success;
}
Status FindNextTask(BehaviourClient& c)
{
Blackboard& blackboard = c.GetBlackboard();
std::shared_ptr<EntityManager> entity_manager = c.GetEntityManager();
std::shared_ptr<World> world = c.GetWorld();
const Position& start = blackboard.Get<Position>("Structure.start");
const Position& end = blackboard.Get<Position>("Structure.end");
const std::vector<std::vector<std::vector<short> > >& target = blackboard.Get<std::vector<std::vector<std::vector<short> > > >("Structure.target");
const std::map<short, std::string>& palette = blackboard.Get<std::map<short, std::string> >("Structure.palette");
const std::set<std::string>& available = blackboard.Get<std::set<std::string> >("Inventory.block_list");
std::mt19937 random_engine(static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()));
Position start_pos;
start_pos.x = std::min(end.x, std::max(start.x, static_cast<int>(std::floor(entity_manager->GetLocalPlayer()->GetX()))));
start_pos.y = std::min(end.y, std::max(start.y, static_cast<int>(std::floor(entity_manager->GetLocalPlayer()->GetY()))));
start_pos.z = std::min(end.z, std::max(start.z, static_cast<int>(std::floor(entity_manager->GetLocalPlayer()->GetZ()))));
std::unordered_set<Position> explored;
std::unordered_set<Position> to_explore;
const std::vector<Position> neighbour_offsets({ Position(0, 1, 0), Position(0, -1, 0),
Position(0, 0, 1), Position(0, 0, -1),
Position(1, 0, 0), Position(-1, 0, 0) });
to_explore.insert(start_pos);
std::vector<Position> pos_candidates;
std::vector<std::string> item_candidates;
std::vector<PlayerDiggingFace> face_candidates;
while (!to_explore.empty())
{
// For each candidate, check if
// 1) the target is not air
// 2) we have the correct block in the inventory
// 3) it is currently a free space
// 4) it has a block under or next to it so we can put the new block
// OR
// 1) the placed block is not air
// 2) it does not match the desired build
// 3) it has a free block under or next to it so we can dig it
for (auto it = to_explore.begin(); it != to_explore.end(); ++it)
{
const Position pos = *it;
const int target_palette = target[pos.x - start.x][pos.y - start.y][pos.z - start.z];
const std::string& target_name = palette.at(target_palette);
const Blockstate* blockstate = world->GetBlock(pos);
if (blockstate == nullptr)
{
#if PROTOCOL_VERSION < 347 /* < 1.13 */
blockstate = AssetsManager::getInstance().Blockstates().at(0).at(0).get();
#else
blockstate = AssetsManager::getInstance().Blockstates().at(0).get();
#endif
}
// Empty space requiring block placement
if (target_palette != -1
&& blockstate->IsAir()
&& available.find(target_name) != available.end())
{
for (int i = 0; i < neighbour_offsets.size(); ++i)
{
const Blockstate* neighbour_blockstate = world->GetBlock(pos + neighbour_offsets[i]);
if (neighbour_blockstate != nullptr && !neighbour_blockstate->IsAir())
{
pos_candidates.push_back(pos);
item_candidates.push_back(target_name);
face_candidates.push_back(static_cast<PlayerDiggingFace>(i));
break;
}
}
}
// Wrong block requiring digging
else if ((target_palette != -1 && !blockstate->IsAir() && target_name != blockstate->GetName())
|| (target_palette == -1 && !blockstate->IsAir()))
{
for (int i = 0; i < neighbour_offsets.size(); ++i)
{
const Blockstate* neighbour_block = world->GetBlock(pos + neighbour_offsets[i]);
if (neighbour_block != nullptr && !neighbour_block->IsAir())
{
pos_candidates.push_back(pos);
item_candidates.push_back("");
face_candidates.push_back((PlayerDiggingFace)i);
break;
}
}
}
}
// If we have at least one candidate
if (pos_candidates.size() > 0)
{
// Get the position of all other players
std::vector<Vector3<double> > other_player_pos;
{
auto entities = entity_manager->GetEntities();
for (const auto& [id, entity] : *entities)
{
if (entity->GetType() == EntityType::Player)
{
other_player_pos.push_back(entity ->GetPosition());
}
}
}
// Get all the candidates that are as far as possible from all
// the other players
std::vector<int> max_dist_indices;
double max_dist = 0.0;
for (int i = 0; i < pos_candidates.size(); ++i)
{
double dist = 0.0;
for (int j = 0; j < other_player_pos.size(); ++j)
{
dist += std::abs(pos_candidates[i].x - other_player_pos[j].x) +
std::abs(pos_candidates[i].y - other_player_pos[j].y) +
std::abs(pos_candidates[i].z - other_player_pos[j].z);
if (dist > max_dist)
{
max_dist_indices.clear();
max_dist = dist;
}
if (dist == max_dist)
{
max_dist_indices.push_back(i);
}
}
}
// Select one randomly if multiple possibilities
int selected_index = max_dist_indices.size() == 1 ? 0 : max_dist_indices[std::uniform_int_distribution<int>(0, static_cast<int>(max_dist_indices.size()) - 1)(random_engine)];
blackboard.Set<std::string>("NextTask.action", item_candidates[selected_index].empty() ? "Dig" : "Place");
blackboard.Set("NextTask.block_position", pos_candidates[selected_index]);
blackboard.Set("NextTask.face", face_candidates[selected_index]);
if (!item_candidates[selected_index].empty())
{
blackboard.Set("NextTask.item", item_candidates[selected_index]);
}
return Status::Success;
}
explored.insert(to_explore.begin(), to_explore.end());
std::unordered_set<Position> neighbours;
for (auto it = to_explore.begin(); it != to_explore.end(); ++it)
{
for (int i = 0; i < neighbour_offsets.size(); ++i)
{
const Position p = *it + neighbour_offsets[i];
if (p.x < start.x ||
p.x > end.x ||
p.y < start.y ||
p.y > end.y ||
p.z < start.z ||
p.z > end.z)
{
continue;
}
if (explored.find(p) == explored.end())
{
neighbours.insert(p);
}
}
}
to_explore = neighbours;
}
return Status::Failure;
}
Status ExecuteNextTask(BehaviourClient& c)
{
Blackboard& b = c.GetBlackboard();
const std::string& action = b.Get<std::string>("NextTask.action");
const Position& block_position = b.Get<Position>("NextTask.block_position");
const PlayerDiggingFace face = b.Get<PlayerDiggingFace>("NextTask.face");
if (action == "Dig")
{
return Dig(c, block_position, true, face);
}
else if (action == "Place")
{
const std::string& item_name = b.Get<std::string>("NextTask.item");
return PlaceBlock(c, item_name, block_position, face, true, false);
}
LOG_WARNING("Unknown task in ExecuteNextTask");
return Status::Failure;
}
Status CheckCompletion(BehaviourClient& c)
{
Blackboard& blackboard = c.GetBlackboard();
std::shared_ptr<World> world = c.GetWorld();
Position world_pos;
Position target_pos;
int additional_blocks = 0;
int wrong_blocks = 0;
int missing_blocks = 0;
const Position& start = blackboard.Get<Position>("Structure.start");
const Position& end = blackboard.Get<Position>("Structure.end");
const std::vector<std::vector<std::vector<short> > >& target = blackboard.Get<std::vector<std::vector<std::vector<short> > > >("Structure.target");
const std::map<short, std::string>& palette = blackboard.Get<std::map<short, std::string> >("Structure.palette");
const bool log_details = blackboard.Get<bool>("CheckCompletion.log_details", false);
const bool log_errors = blackboard.Get<bool>("CheckCompletion.log_errors", false);
const bool full_check = blackboard.Get<bool>("CheckCompletion.full_check", false);
//Reset values for the next time
blackboard.Set("CheckCompletion.log_details", false);
blackboard.Set("CheckCompletion.log_errors", false);
blackboard.Set("CheckCompletion.full_check", false);
for (int x = start.x; x <= end.x; ++x)
{
world_pos.x = x;
target_pos.x = x - start.x;
for (int y = start.y; y <= end.y; ++y)
{
world_pos.y = y;
target_pos.y = y - start.y;
for (int z = start.z; z <= end.z; ++z)
{
world_pos.z = z;
target_pos.z = z - start.z;
const short target_id = target[target_pos.x][target_pos.y][target_pos.z];
const Blockstate* blockstate = world->GetBlock(world_pos);
if (blockstate == nullptr)
{
if (target_id != -1)
{
if (!full_check)
{
return Status::Failure;
}
missing_blocks++;
if (log_details && missing_blocks < 100) // Don't print more than 100 missing blocks
{
LOG_INFO("Missing " << palette.at(target_id) << " in " << world_pos);
}
}
continue;
}
if (target_id == -1)
{
if (!blockstate->IsAir())
{
if (!full_check)
{
return Status::Failure;
}
additional_blocks++;
if (log_details)
{
LOG_INFO("Additional " << blockstate->GetName() << " in " << world_pos);
}
}
}
else
{
if (blockstate->IsAir())
{
if (!full_check)
{
return Status::Failure;
}
missing_blocks++;
if (log_details)
{
LOG_INFO("Missing " << palette.at(target_id) << " in " << world_pos);
}
}
else
{
const std::string& target_name = palette.at(target_id);
if (blockstate->GetName() != target_name)
{
if (!full_check)
{
return Status::Failure;
}
wrong_blocks++;
if (log_details)
{
LOG_INFO("Wrong " << blockstate->GetName() << " instead of " << target_name << " in " << world_pos);
}
}
}
}
}
}
}
if (log_errors)
{
LOG_INFO("Wrong blocks: " << wrong_blocks);
LOG_INFO("Missing blocks: " << missing_blocks);
LOG_INFO("Additional blocks: " << additional_blocks);
}
return (missing_blocks + additional_blocks + wrong_blocks == 0) ? Status::Success : Status::Failure;
}
Status WarnConsole(BehaviourClient& c, const std::string& msg)
{
LOG_WARNING("[" << c.GetNetworkManager()->GetMyName() << "]: " << msg);
return Status::Success;
}
Status LoadNBT(BehaviourClient& c, const std::string& path, const Position& offset, const std::string& temp_block, const bool log_info)
{
NBT::Value loaded_file;
try
{
std::ifstream infile(path, std::ios_base::binary);
infile.unsetf(std::ios::skipws);
infile >> loaded_file;
infile.close();
}
catch (const std::exception& e)
{
LOG_ERROR("Error loading NBT file " << e.what());
return Status::Failure;
}
std::map<short, std::string> palette;
palette[-1] = "minecraft:air";
short id_temp_block = -1;
std::map<short, int> num_blocks_used;
if (!loaded_file.contains("palette") ||
!loaded_file["palette"].is_list_of<NBT::TagCompound>())
{
LOG_ERROR("Error loading NBT file, no palette TagCompound found");
return Status::Failure;
}
const std::vector<NBT::TagCompound>& palette_list = loaded_file["palette"].as_list_of<NBT::TagCompound>();
for (int i = 0; i < palette_list.size(); ++i)
{
const std::string& block_name = palette_list[i]["Name"].get<std::string>();
palette[i] = block_name;
num_blocks_used[i] = 0;
if (block_name == temp_block)
{
id_temp_block = i;
}
}
Position min(std::numeric_limits<int>().max(), std::numeric_limits<int>().max(), std::numeric_limits<int>().max());
Position max(std::numeric_limits<int>().min(), std::numeric_limits<int>().min(), std::numeric_limits<int>().min());
if (!loaded_file.contains("blocks") ||
!loaded_file["blocks"].is_list_of<NBT::TagCompound>())
{
LOG_ERROR("Error loading NBT file, no blocks TagCompound found");
return Status::Failure;
}
const std::vector<NBT::TagCompound>& block_tag = loaded_file["blocks"].as_list_of<NBT::TagCompound>();
for (const auto& c : block_tag)
{
const std::vector<int>& pos_list = c["pos"].as_list_of<int>();
const int x = pos_list[0];
const int y = pos_list[1];
const int z = pos_list[2];
if (x < min.x)
{
min.x = x;
}
if (y < min.y)
{
min.y = y;
}
if (z < min.z)
{
min.z = z;
}
if (x > max.x)
{
max.x = x;
}
if (y > max.y)
{
max.y = y;
}
if (z > max.z)
{
max.z = z;
}
}
Position size = max - min + Position(1, 1, 1);
Position start = offset;
Position end = offset + size - Position(1, 1, 1);
if (log_info)
{
LOG_INFO("Start: " << start << " | " << "End: " << end);
}
// Fill the target area with air (-1)
std::vector<std::vector<std::vector<short> > > target(size.x, std::vector<std::vector<short> >(size.y, std::vector<short>(size.z, -1)));
// Read all block to place
for (const auto& c : block_tag)
{
const int state = c["state"].get<int>();
const std::vector<int>& pos_list = c["pos"].as_list_of<int>();
const int x = pos_list[0];
const int y = pos_list[1];
const int z = pos_list[2];
target[x - min.x][y - min.y][z - min.z] = state;
num_blocks_used[state] += 1;
}
if (id_temp_block == -1)
{
LOG_WARNING("Can't find the given temp block " << temp_block << " in the palette");
}
else
{
int removed_layers = 0;
// Check the bottom Y layers, if only
// air or temp block, the layer can be removed
while (true)
{
bool is_removable = true;
int num_temp_block = 0;
for (int x = 0; x < size.x; ++x)
{
for (int z = 0; z < size.z; z++)
{
if (target[x][0][z] == id_temp_block)
{
num_temp_block += 1;
}
if (target[x][0][z] != -1 &&
target[x][0][z] != id_temp_block)
{
is_removable = false;
break;
}
if (!is_removable)
{
break;
}
}
}
if (!is_removable)
{
break;
}
for (int x = 0; x < size.x; ++x)
{
target[x].erase(target[x].begin());
}
num_blocks_used[id_temp_block] -= num_temp_block;
removed_layers++;
size.y -= 1;
end.y -= 1;
}
if (log_info)
{
LOG_INFO("Removed the bottom " << removed_layers << " layer" << (removed_layers > 1 ? "s" : ""));
}
}
if (log_info)
{
LOG_INFO("Total size: " << size);
std::stringstream needed;
needed << "Block needed:\n";
for (auto it = num_blocks_used.begin(); it != num_blocks_used.end(); ++it)
{
needed << "\t" << palette[it->first] << "\t\t" << it->second << "\n";
}
LOG_INFO(needed.rdbuf());
// Check if some block can't be placed (flying blocks)
std::stringstream flyings;
flyings << "Flying blocks, you might have to place them yourself:\n";
Position target_pos;
const std::vector<Position> neighbour_offsets({ Position(0, 1, 0), Position(0, -1, 0),
Position(0, 0, 1), Position(0, 0, -1),
Position(1, 0, 0), Position(-1, 0, 0) });
for (int x = 0; x < size.x; ++x)
{
target_pos.x = x;
// If this block is on the floor, it's ok
for (int y = 1; y < size.y; ++y)
{
target_pos.y = y;
for (int z = 0; z < size.z; ++z)
{
target_pos.z = z;
const short target_id = target[target_pos.x][target_pos.y][target_pos.z];
if (target_id != -1)
{
// Check all target neighbours
bool has_neighbour = false;
for (int i = 0; i < neighbour_offsets.size(); ++i)
{
const Position neighbour_pos = target_pos + neighbour_offsets[i];
if (neighbour_pos.x >= 0 && neighbour_pos.x < size.x &&
neighbour_pos.y >= 0 && neighbour_pos.y < size.y &&
neighbour_pos.z >= 0 && neighbour_pos.z < size.z &&
target[neighbour_pos.x][neighbour_pos.y][neighbour_pos.z] != -1)
{
has_neighbour = true;
break;
}
}
if (!has_neighbour)
{
flyings << start + target_pos << "\t" << palette[target_id] << "\n";
}
}
}
}
}
LOG_WARNING(flyings.rdbuf());
}
Blackboard& blackboard = c.GetBlackboard();
blackboard.Set("Structure.start", start);
blackboard.Set("Structure.end", end);
blackboard.Set("Structure.target", target);
blackboard.Set("Structure.palette", palette);
blackboard.Set("Structure.loaded", true);
return Status::Success;
}
@@ -0,0 +1,370 @@
#include <iostream>
#include <string>
#include "botcraft/Game/World/World.hpp"
#include "botcraft/AI/BehaviourTree.hpp"
#include "botcraft/AI/Tasks/AllTasks.hpp"
#include "botcraft/AI/SimpleBehaviourClient.hpp"
#include "botcraft/Utilities/Logger.hpp"
#include "botcraft/Utilities/SleepUtilities.hpp"
#include "CustomBehaviourTree.hpp"
#include "MapCreationTasks.hpp"
using namespace Botcraft;
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--numbot\tNumber of parallel bot to start, default: 5\n"
<< "\t--numworld\tNumber of parallel world to use, less worlds saves RAM, but slows the bots down, default: 1\n"
<< "\t--nbt\tnbt filename to load, default: empty\n"
<< "\t--offset\t3 ints, offset for the first block, default: 0 0 0\n"
<< "\t--tempblock\tname of the scaffolding block, default: minecraft:slime_block\n"
<< std::endl;
}
struct Args
{
bool help = false;
std::string address = "127.0.0.1:25565";
int num_bot = 5;
int num_world = 1;
std::string nbt_file = "";
Position offset = Position(0, 0, 0);
std::string temp_block = "minecraft:slime_block";
int return_code = 0;
};
Args ParseCommandLine(int argc, char* argv[]);
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> GenerateMapArtCreatorTree(const std::string& food_name,
const std::string& nbt_path, const Position& offset, const std::string& temp_block, const bool detailed);
int main(int argc, char* argv[])
{
try
{
// Init logging, log everything >= Info, only to console, no file
Logger::GetInstance().SetLogLevel(LogLevel::Info);
Logger::GetInstance().SetFilename("");
// Add a name to this thread for logging
Logger::GetInstance().RegisterThread("main");
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;
}
}
const std::vector<std::string> base_names = { "BotAuFeu", "Botager", "Botiron", "BotEnTouche", "BotDeVin", "BotAuxRoses", "BotronMinet", "Botmobile", "Botman", "Botentiel" };
auto map_art_detailed_behaviour_tree = GenerateMapArtCreatorTree("minecraft:golden_carrot", args.nbt_file, args.offset, args.temp_block, true);
auto map_art_behaviour_tree = GenerateMapArtCreatorTree("minecraft:golden_carrot", args.nbt_file, args.offset, args.temp_block, false);
std::vector<std::shared_ptr<World> > shared_worlds(args.num_world);
for (int i = 0; i < args.num_world; i++)
{
shared_worlds[i] = std::make_shared<World>(true);
}
std::vector<std::string> names(args.num_bot);
std::vector<std::shared_ptr<SimpleBehaviourClient> > clients(args.num_bot);
for (int i = 0; i < args.num_bot; ++i)
{
names[i] = base_names[i] + (i < base_names.size() ? "" : ("_" + std::to_string(i / base_names.size())));
clients[i] = std::make_shared<SimpleBehaviourClient>(false);
clients[i]->SetSharedWorld(shared_worlds[i % args.num_world]);
clients[i]->SetAutoRespawn(true);
clients[i]->Connect(args.address, names[i], false);
clients[i]->StartBehaviour();
clients[i]->SetBehaviourTree(i == 0 ? map_art_detailed_behaviour_tree : map_art_behaviour_tree);
}
std::map<int, std::chrono::steady_clock::time_point> restart_time;
std::chrono::steady_clock::time_point next_time_display = std::chrono::steady_clock::now() + std::chrono::minutes(2);
while (true)
{
// Check if any client should be closed, and schedule the restart 10 seconds later
for (int i = 0; i < clients.size(); ++i)
{
if (clients[i]->GetShouldBeClosed())
{
clients[i]->Disconnect();
clients[i].reset();
clients[i] = std::make_shared<SimpleBehaviourClient>(false);
clients[i]->SetSharedWorld(shared_worlds[i % args.num_world]);
clients[i]->SetAutoRespawn(true);
clients[i]->Connect(args.address, names[i], false);
// Restart client[i] in 10 seconds
LOG_INFO(names[i] << " has been stopped. Scheduling a restart in 10 seconds...");
restart_time[i] = std::chrono::steady_clock::now() + std::chrono::seconds(10);
}
}
// Check if any scheduled restart should happen
auto now = std::chrono::steady_clock::now();
for (auto it = restart_time.begin(); it != restart_time.end(); )
{
if (now > it->second)
{
LOG_INFO("Restarting " << names[it->first] << "...");
clients[it->first]->StartBehaviour();
clients[it->first]->SetBehaviourTree(map_art_behaviour_tree);
restart_time.erase(it++);
}
else
{
++it;
}
}
// Check if we should display progress details
if (now > next_time_display)
{
Blackboard& blackboard = clients[0]->GetBlackboard();
// Set blackboard values to true so the next check completion will print the details
blackboard.Set<bool>("CheckCompletion.print_details", true);
blackboard.Set<bool>("CheckCompletion.print_errors", true);
blackboard.Set<bool>("CheckCompletion.full_check", true);
next_time_display += std::chrono::minutes(2);
}
std::chrono::steady_clock::time_point end = now + std::chrono::milliseconds(10);
for (int i = 0; i < clients.size(); ++i)
{
clients[i]->BehaviourStep();
}
Utilities::SleepUntil(end);
}
return 0;
}
catch (std::exception& e)
{
LOG_FATAL("Exception: " << e.what());
return 1;
}
catch (...)
{
LOG_FATAL("Unknown exception");
return 2;
}
}
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 == "--numbot")
{
if (i + 1 < argc)
{
args.num_bot = std::stoi(argv[++i]);
}
else
{
LOG_FATAL("--numbot requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--numworld")
{
if (i + 1 < argc)
{
args.num_world = std::stoi(argv[++i]);
}
else
{
LOG_FATAL("--numworld requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--nbt")
{
if (i + 1 < argc)
{
args.nbt_file = argv[++i];
}
else
{
LOG_FATAL("--nbt requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--offset")
{
if (i + 3 < argc)
{
int x = std::stoi(argv[++i]);
int y = std::stoi(argv[++i]);
int z = std::stoi(argv[++i]);
args.offset = Position(x, y, z);
}
else
{
LOG_FATAL("--offset requires 3 arguments");
args.return_code = 1;
return args;
}
}
else if (arg == "--tempblock")
{
if (i + 1 < argc)
{
args.temp_block = argv[++i];
}
else
{
LOG_FATAL("--tempblock requires an argument");
args.return_code = 1;
return args;
}
}
}
return args;
}
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> GenerateMapArtCreatorTree(const std::string& food_name,
const std::string& nbt_path, const Position& offset, const std::string& temp_block, const bool detailed)
{
auto loading_tree = Builder<SimpleBehaviourClient>("loading")
.selector()
// Check if the structure is already loaded
.leaf(CheckBlackboardBoolData, "Structure.loaded")
// Otherwise load it
.leaf("load NBT file", LoadNBT, nbt_path, offset, temp_block, detailed)
.end();
auto completion_tree = Builder<SimpleBehaviourClient>("completion check")
.succeeder().sequence()
.leaf("check completion", CheckCompletion)
.leaf(WarnConsole, "Task fully completed!")
.repeater(0).inverter().leaf(Yield)
.end();
auto disconnect_subtree = Builder<SimpleBehaviourClient>("disconnect")
.sequence()
.leaf("disconnect", Disconnect)
.repeater(0).inverter().leaf(Yield)
.end();
auto eat_subtree = Builder<SimpleBehaviourClient>("eat")
.selector()
// If hungry
.inverter().leaf(IsHungry, 20)
// Get some food, then eat
.sequence()
.selector()
.leaf(SetItemInHand, food_name, Hand::Left)
.sequence()
.leaf("get food", GetSomeFood, food_name)
.leaf(SetItemInHand, food_name, Hand::Left)
.end()
.leaf(WarnConsole, "Can't find food anywhere!")
.end()
.selector()
.leaf(Eat, food_name, true)
.inverter().leaf(WarnConsole, "Can't eat!")
// If we are here, hungry and can't eat --> Disconnect
.tree(disconnect_subtree)
.end()
.end()
.end();
auto getinventory_tree = Builder<SimpleBehaviourClient>("list blocks in inventory")
// List all blocks in the inventory
.selector()
.leaf("get block in inventory", GetBlocksAvailableInInventory)
// If no block found, get some in neighbouring chests
.sequence()
.selector()
.leaf("get some blocks from chests", SwapChestsInventory, food_name, true)
.inverter().leaf(WarnConsole, "Can't swap with chests, will wait before retrying.")
// If the previous task failed, maybe chests were just
// not loaded yet, sleep for ~1 second
.inverter().repeater(100).leaf(Yield)
.end()
.selector()
.leaf("get block in inventory after swapping", GetBlocksAvailableInInventory)
.inverter().leaf(WarnConsole, "No more block in chests, I will stop here.")
.tree(disconnect_subtree)
.end()
.end()
.end();
auto placeblock_tree = Builder<SimpleBehaviourClient>("place block")
.selector()
// Try to perform a task 5 times
.decorator<RepeatUntilSuccess<SimpleBehaviourClient>>(5).selector()
.sequence()
.leaf("find next task", FindNextTask)
.leaf("execute next task", ExecuteNextTask)
.end()
// If the previous task failed, sleep for ~1 second
// before retrying to get an action
.inverter().repeater(100).leaf(Yield)
.end()
// If failed 5 times, put all blocks in chests to
// randomize available blocks for next time
.leaf("dump all items in chest", SwapChestsInventory, food_name, false)
.end();
return Builder<SimpleBehaviourClient>("main")
// Main sequence of actions
.sequence()
.tree(loading_tree)
.tree(completion_tree)
.tree(eat_subtree)
.tree(getinventory_tree)
.tree(placeblock_tree)
.end();
}
@@ -0,0 +1,13 @@
project(5_MobHitterExample)
set(${PROJECT_NAME}_SOURCE_FILES
${PROJECT_SOURCE_DIR}/include/MobHitterTasks.hpp
${PROJECT_SOURCE_DIR}/src/MobHitterTasks.cpp
${PROJECT_SOURCE_DIR}/src/main.cpp
)
set(${PROJECT_NAME}_INCLUDE_FOLDERS
${PROJECT_SOURCE_DIR}/include
)
add_example("${${PROJECT_NAME}_INCLUDE_FOLDERS}" "${${PROJECT_NAME}_SOURCE_FILES}")
@@ -0,0 +1,16 @@
#pragma once
#include <string>
#include "botcraft/AI/BehaviourClient.hpp"
#include "botcraft/AI/Status.hpp"
/// @brief Hit all nearby monster, respecting the invulnerability cooldown of 0.5s
/// @param c The client performing the action
/// @return Always return Success
Botcraft::Status HitCloseHostiles(Botcraft::BehaviourClient& c);
/// @brief Remove entries in Entities.LastTimeHit if last seen > 10s
/// @param c The client performing the action
/// @return Always return Success
Botcraft::Status CleanLastTimeHit(Botcraft::BehaviourClient& c);
@@ -0,0 +1,77 @@
#include "MobHitterTasks.hpp"
#include "botcraft/Game/Entities/EntityManager.hpp"
#include "botcraft/Game/Entities/LocalPlayer.hpp"
#include "botcraft/Network/NetworkManager.hpp"
using namespace Botcraft;
using namespace ProtocolCraft;
Status HitCloseHostiles(BehaviourClient& c)
{
std::shared_ptr<EntityManager> entity_manager = c.GetEntityManager();
std::shared_ptr<LocalPlayer> local_player = entity_manager->GetLocalPlayer();
std::shared_ptr<NetworkManager> network_manager = c.GetNetworkManager();
Blackboard& blackboard = c.GetBlackboard();
const NotifyOnEndUseRef<std::map<int, std::chrono::steady_clock::time_point>> last_time_hit_wrapper = blackboard.GetRef("Entities.LastTimeHit", std::map<int, std::chrono::steady_clock::time_point>());
std::map<int, std::chrono::steady_clock::time_point>& last_time_hit = last_time_hit_wrapper.ref();
const Vector3<double> player_pos = local_player->GetPosition();
auto now = std::chrono::steady_clock::now();
{
auto entities = entity_manager->GetEntities();
for (const auto& [id, entity] : *entities)
{
if (entity->IsMonster() && (entity->GetPosition()- player_pos).SqrNorm() < 16.0)
{
auto time = last_time_hit.find(id);
if (time != last_time_hit.end() &&
std::chrono::duration_cast<std::chrono::milliseconds>(now - time->second).count() < 500)
{
continue;
}
last_time_hit[id] = now;
local_player->LookAt(entity->GetPosition());
std::shared_ptr<ServerboundInteractPacket> msg = std::make_shared<ServerboundInteractPacket>();
msg->SetAction(1);
msg->SetEntityId(id);
#if PROTOCOL_VERSION > 722 /* > 1.15.2 */
msg->SetUsingSecondaryAction(false);
#endif
std::shared_ptr<ServerboundSwingPacket> msg_swing = std::make_shared<ServerboundSwingPacket>();
msg_swing->SetHand(0);
network_manager->Send(msg);
network_manager->Send(msg_swing);
}
}
}
return Status::Success;
}
Status CleanLastTimeHit(BehaviourClient& c)
{
const NotifyOnEndUseRef<std::map<int, std::chrono::steady_clock::time_point>> last_time_hit_wrapper = c.GetBlackboard().GetRef("Entities.LastTimeHit", std::map<int, std::chrono::steady_clock::time_point>());
std::map<int, std::chrono::steady_clock::time_point>& last_time_hit = last_time_hit_wrapper.ref();
auto now = std::chrono::steady_clock::now();
for (auto it = last_time_hit.begin(); it != last_time_hit.end();)
{
if (std::chrono::duration_cast<std::chrono::seconds>(now - it->second).count() > 10)
{
it = last_time_hit.erase(it);
}
else
{
++it;
}
}
return Status::Success;
}
@@ -0,0 +1,132 @@
#include <iostream>
#include <string>
#include "botcraft/AI/SimpleBehaviourClient.hpp"
#include "botcraft/Utilities/Logger.hpp"
#include "MobHitterTasks.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: BCChatCommand\n"
<< std::endl;
}
struct Args
{
bool help = false;
std::string address = "127.0.0.1:25565";
std::string login = "BCMobHitter";
int return_code = 0;
};
Args ParseCommandLine(int argc, char* argv[]);
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");
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 mob_hitter_tree = Botcraft::Builder<Botcraft::SimpleBehaviourClient>()
.sequence()
.leaf("hit close hostiles mobs", HitCloseHostiles)
.leaf("remove old entities from last time hit", CleanLastTimeHit)
.end();
Botcraft::SimpleBehaviourClient client(true);
client.SetAutoRespawn(true);
LOG_INFO("Starting connection process");
client.Connect(args.address, args.login);
client.SetBehaviourTree(mob_hitter_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;
}
}
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;
}
@@ -0,0 +1,13 @@
project(6_DispenserFarmExample)
set(${PROJECT_NAME}_SOURCE_FILES
${PROJECT_SOURCE_DIR}/include/DispenserFarmTasks.hpp
${PROJECT_SOURCE_DIR}/src/DispenserFarmTasks.cpp
${PROJECT_SOURCE_DIR}/src/main.cpp
)
set(${PROJECT_NAME}_INCLUDE_FOLDERS
${PROJECT_SOURCE_DIR}/include
)
add_example("${${PROJECT_NAME}_INCLUDE_FOLDERS}" "${${PROJECT_NAME}_SOURCE_FILES}")
@@ -0,0 +1,85 @@
#pragma once
#include <string>
#include <random>
#include <chrono>
#include "botcraft/AI/BehaviourClient.hpp"
#include "botcraft/AI/Status.hpp"
/// @brief Check condition
/// @param client The client performing the action
/// @param radius The max distance to search blocks
/// @return Always return Success
Botcraft::Status InitializeBlocks(Botcraft::BehaviourClient& client, const int radius = 50);
/// @brief Turn off the lights of the spawners if not already done
/// @param client The client performing the action
/// @return Success if the lever is correctly pulled, Failure otherwise
Botcraft::Status StartSpawners(Botcraft::BehaviourClient& client);
/// @brief Check if any mob is wearing frost walker boots
/// @param client The client performing the action
/// @return Success if there is one such mob, Failure otherwise
Botcraft::Status CheckFrostWalkerMob(Botcraft::BehaviourClient& client);
/// @brief Remove all items except item_to_keep from a chest, destroying the others on a cactus
/// @param client The client performing the action
/// @param chest_pos_blackboard String name of the position of the chest to process in the blackboard
/// @param item_to_keep Name of the item we want to keep
/// @return Success if the operation went smoothly, Failure otherwise
Botcraft::Status CleanChest(Botcraft::BehaviourClient& client, const std::string& chest_pos_blackboard, const std::string& item_to_keep);
/// @brief Store a dispenser in the chest found during initialization
/// @param client The client performing the action
/// @return Success if the dispenser is placed in the chest, Failure otherwise
Botcraft::Status StoreDispenser(Botcraft::BehaviourClient& client);
/// @brief Get one cobblestone from the stone generator
/// @param client The client performing the action
/// @return Success if one cobblestone is acquired, Failure otherwise
Botcraft::Status MineCobblestone(Botcraft::BehaviourClient& client);
/// @brief Take item in chest until there are N in inventory
/// @param client The client performing the action
/// @param item_name The item to take
/// @param N Number in inventory
/// @return Success if N in inventory, Failure otherwise
Botcraft::Status TakeFromChest(Botcraft::BehaviourClient& client, const std::string& item_name, const int N);
/// @brief Copy a random element of a std::vector<T> to a blackboard location
/// @tparam T Type of the element
/// @param client The client performing the action
/// @param src Blackboard source vector location
/// @param dst Blackboard single element destination
/// @return Always return success
template<typename T>
Botcraft::Status CopyRandomFromVectorBlackboardData(Botcraft::BehaviourClient& client, const std::string& src, const std::string& dst)
{
Botcraft::Blackboard& blackboard = client.GetBlackboard();
const std::vector<T>& source = blackboard.Get<std::vector<T>>(src);
if (source.size() == 1)
{
blackboard.Set<T>(dst, source[0]);
return Botcraft::Status::Success;
}
std::mt19937 random_engine(static_cast<unsigned int>(std::chrono::high_resolution_clock::now().time_since_epoch().count()));
blackboard.Set<T>(dst, source[std::uniform_int_distribution<>(0, static_cast<int>(source.size()) - 1)(random_engine)]);
return Botcraft::Status::Success;
}
/// @brief Break carrots/potatoes, collect the items, replant
/// @param client The client performing the action
/// @param blocks_pos_blackboard Blackboard address of the vector of positions of blocks
/// @param item_name Name of the item we want to collect
/// @return Success if blocks are broken, items gathered and crops replanted, Failure otherwise
Botcraft::Status CollectCropsAndReplant(Botcraft::BehaviourClient& client, const std::string& blocks_pos_blackboard, const std::string& item_name);
/// @brief Use the cactus to destroy any of these items in the inventory
/// @param client The client performing the action
/// @param item_name The item to remove from the inventory
/// @return Success if no more items in inventory, Failure otherwise
Botcraft::Status DestroyItems(Botcraft::BehaviourClient& client, const std::string& item_name);
@@ -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();
}
@@ -0,0 +1,19 @@
project(7_WorldEaterExample)
set(${PROJECT_NAME}_SOURCE_FILES
${PROJECT_SOURCE_DIR}/include/WorldEaterClient.hpp
${PROJECT_SOURCE_DIR}/include/WorldEaterSubTrees.hpp
${PROJECT_SOURCE_DIR}/include/WorldEaterTasks.hpp
${PROJECT_SOURCE_DIR}/include/WorldEaterUtilities.hpp
${PROJECT_SOURCE_DIR}/src/main.cpp
${PROJECT_SOURCE_DIR}/src/WorldEaterClient.cpp
${PROJECT_SOURCE_DIR}/src/WorldEaterSubTrees.cpp
${PROJECT_SOURCE_DIR}/src/WorldEaterTasks.cpp
${PROJECT_SOURCE_DIR}/src/WorldEaterUtilities.cpp
)
set(${PROJECT_NAME}_INCLUDE_FOLDERS
${PROJECT_SOURCE_DIR}/include
)
add_example("${${PROJECT_NAME}_INCLUDE_FOLDERS}" "${${PROJECT_NAME}_SOURCE_FILES}")
@@ -0,0 +1,28 @@
#pragma once
#include "botcraft/AI/SimpleBehaviourClient.hpp"
#include <string>
/// @brief Very simple extension of Botcraft::SimpleBehaviourClient.
/// The only addition is a Handle function to react to a specific
/// word in the chat, which will trigger the pause of the bots.
class WorldEaterClient : public Botcraft::SimpleBehaviourClient
{
public:
WorldEaterClient(const std::string& trigger_word, const bool use_renderer_);
~WorldEaterClient();
protected:
#if PROTOCOL_VERSION < 759 /* < 1.19 */
virtual void Handle(ProtocolCraft::ClientboundChatPacket& msg) override;
#else
virtual void Handle(ProtocolCraft::ClientboundPlayerChatPacket& msg) override;
virtual void Handle(ProtocolCraft::ClientboundSystemChatPacket& msg) override;
#endif
void ProcessChatMsg(const std::string& msg);
private:
std::string word;
};
@@ -0,0 +1,14 @@
#pragma once
#include <botcraft/AI/Tasks/AllTasks.hpp>
#include <botcraft/AI/SimpleBehaviourClient.hpp>
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> FullTree();
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> IsDeadTree();
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> InitTree();
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> BaseCampResupplyTree();
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> MainTree();
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> ActionLoopTree();
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> CompletionTree();
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> GoToTopLadderTree();
@@ -0,0 +1,87 @@
#pragma once
#include <botcraft/AI/SimpleBehaviourClient.hpp>
/// @brief Initialize all the blackboard values required to run the tree
/// @param client The client performing the action
/// @return Success if everything was initialize properly, failure otherwise
Botcraft::Status Init(Botcraft::SimpleBehaviourClient& client);
/// @brief Get the next action in the action queue
/// @param client The client performing the action
/// @return Failure if there was no action in the queue, Success otherwise
Botcraft::Status GetNextAction(Botcraft::SimpleBehaviourClient& client);
/// @brief Execute the currently loaded action
/// @param client The client performing the action
/// @return Success if the block was placed/removed, Failure otherwise
Botcraft::Status ExecuteAction(Botcraft::SimpleBehaviourClient& client);
/// @brief Check if the action queue is empty
/// @param client The client performing the action
/// @return Success if the action queue is empty, Failure otherwise
Botcraft::Status CheckActionsDone(Botcraft::SimpleBehaviourClient& client);
/// @brief Check all layers above the current one to see if we didn't forget a block. This should not happen,
/// but in case it happens for any reason, we want to make sure we detect it and go back up.
/// @param client The client performing the action
/// @return Always returns Success, Eater.current_layer might be updated
Botcraft::Status ValidateCurrentLayer(Botcraft::SimpleBehaviourClient& client);
/// @brief Remove the first action in the action queue
/// @param client The client performing the action
/// @return Failure if the queue is full, Success otherwise
Botcraft::Status PopAction(Botcraft::SimpleBehaviourClient& client);
/// @brief Check if there is some empty slots in the inventory
/// @param client The client performing the action
/// @return Success if the inventory has no more empty slot, Failure otherwise
Botcraft::Status IsInventoryFull(Botcraft::SimpleBehaviourClient& client);
/// @brief Clean the inventory by throwing on the floor all the undesired items and put some lava on top
/// @param client The client performing the action
/// @return Success if the items were thrown, the lava placed and retrieved, Failure otherwise
Botcraft::Status CleanInventory(Botcraft::SimpleBehaviourClient& client);
/// @brief Decrement current layer index
/// @param client The client performing the action
/// @return Success if not done, Failure if we passed the last layer
Botcraft::Status MoveToNextLayer(Botcraft::SimpleBehaviourClient& client);
/// @brief Plan actions to prepare for the current layer (place ladders)
/// @param client The client performing the action
/// @return Always return Success
Botcraft::Status PrepareLayer(Botcraft::SimpleBehaviourClient& client);
/// @brief Plan actions to fill all fluids from the current layer with solid blocks
/// @param client The client performing the action
/// @return Always return Success
Botcraft::Status PlanLayerFluids(Botcraft::SimpleBehaviourClient& client);
/// @brief Plan actions to replace all non walkable blocks (non solid/hazardous) from the current layer with solid ones
/// @param client The client performing the action
/// @return Always return Success
Botcraft::Status PlanLayerNonWalkable(Botcraft::SimpleBehaviourClient& client);
/// @brief Plan actions to remove all blocks from the current layer
/// @param client The client performing the action
/// @return Always return Success
Botcraft::Status PlanLayerBlocks(Botcraft::SimpleBehaviourClient& client);
/// @brief Drop all unnecessary items at basecamp
/// @param client The client performing the action
/// @param all_items If true, all items will be thrown (including tools and food)
/// @return Success if nothing bad happened during dropping
Botcraft::Status BaseCampDropItems(Botcraft::SimpleBehaviourClient& client, const bool all_items);
/// @brief Pick all necessary items from basecamp
/// @param client The client performing the action
/// @return Success if nothing bad happened during picking
Botcraft::Status BaseCampPickItems(Botcraft::SimpleBehaviourClient& client);
/// @brief Check if there is a tool of type tool_type in the inventory
/// @param client The client performing the action
/// @param tool_type Tool type to search for
/// @param min_durability Number of time we can use the tool before it breaks
/// @return Success if a tool with >min_durability durability was found, Failure otherwise
Botcraft::Status HasToolInInventory(Botcraft::SimpleBehaviourClient& client, const Botcraft::ToolType tool_type, const int min_durability = 0);
@@ -0,0 +1,68 @@
#pragma once
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <string>
#include <botcraft/Game/Vector3.hpp>
#include <botcraft/Game/World/World.hpp>
#include <botcraft/AI/SimpleBehaviourClient.hpp>
/// @brief Split a string into subcomponents
/// @param s String to split
/// @param delimiter Delimiter character
/// @return An unordered set of substrings in s
std::unordered_set<std::string> SplitString(const std::string& s, const char delimiter);
/// @brief Find all interest points in the surroundings and load them in the blackboard
/// @param client The client performing the action
/// @return True if everything was found, false otherwise
bool IdentifyBaseCampLayout(Botcraft::SimpleBehaviourClient& client);
/// @brief Helper function. Get all blocks between start and end positions. Assumes start.y == end.y
/// @param world World to query
/// @param start Start position
/// @param end End position
/// @param layer Y position of the current layer
/// @param fluids If true, will gather fluid blocks
/// @param solids If true, will gather solid blocks
/// @return A set of positions respecting the given constraints
std::unordered_set<Botcraft::Position> CollectBlocks(const std::shared_ptr<Botcraft::World> world, const Botcraft::Position& start, const Botcraft::Position& end, const int layer, const bool fluids, const bool solids);
/// @brief Helper function. Group a set of positions on the same layer in connected components
/// @param start Start block of the layer
/// @param end End block of the layer
/// @param start_point Starting point on the layer (typically the access ladder)
/// @param client Client used to check for pathfinding reachability
/// @param positions Positions to process
/// @param additional_neighbours If set, will be filled with additional neighbours for each components (i.e. blocks that are not directly next to each other but still reachable from each other by pathfinding algorithm)
/// @return A vector of connected components
std::vector<std::unordered_set<Botcraft::Position>> GroupBlocksInComponents(const Botcraft::Position& start, const Botcraft::Position& end, const Botcraft::Position& start_point, const Botcraft::BehaviourClient& client, const std::unordered_set<Botcraft::Position>& positions, std::unordered_map<Botcraft::Position, std::unordered_set<Botcraft::Position>>* additional_neighbours);
/// @brief Given a set of connected components, get a list of blocks to add to link them together
/// @param components The components (i.e. group of blocks) to link together
/// @param start_point Starting point on the layer (typically the access ladder)
/// @param bound_min Min bound of the working area (to be sure we don't add block outside of it)
/// @param bound_max Max bound of the working area (to be sure we don't add block outside of it)
/// @return An ordered vector of blocks to add to the layer to link all components together
std::vector<Botcraft::Position> GetBlocksToAdd(const std::vector<std::unordered_set<Botcraft::Position>>& components, const Botcraft::Position& start_point, const Botcraft::Position& bound_min, const Botcraft::Position& bound_max);
/// @brief Merge sets of positions into one set
/// @param components Connected components positions
/// @param additional_blocks Additional blocks to link the components together
/// @return One set of all sets merged
std::unordered_set<Botcraft::Position> FlattenComponentsAndAdditionalBlocks(const std::vector<std::unordered_set<Botcraft::Position>>& components, const std::vector<Botcraft::Position>& additional_blocks);
/// @brief Order all given positions such as we go one by one until we reach exit_block without ever "cutting the branch we are sitting on"
/// @param blocks All block to order. Intentional copy as we need to remove the blocks one by one once processed
/// @param start_block Block to start with
/// @param orientation Orientation of the work area compared to the entry point (North, South, East or West), used to "optimize" the ordering along one axis rather than the other and minimize travel time
/// @param additional_neighbours Potential additional neighbours for some blocks (i.e. blocks that are not direct neighbours but that we can reach by pathfinding)
/// @return Ordered positions to follow, should end with exit_block
std::vector<Botcraft::Position> ComputeBlockOrder(std::unordered_set<Botcraft::Position> blocks, const Botcraft::Position& start_block, const Botcraft::Direction orientation, const std::unordered_map<Botcraft::Position, std::unordered_set<Botcraft::Position>>& additional_neighbours = {});
/// @brief A function to check if a block could turn into falling entity if updated (sand/gravel)
/// @param block_name Block name to check
/// @return True if sand/gravel or similar, false otherwise
bool CouldFallIfUpdated(const std::string& block_name);
@@ -0,0 +1,76 @@
#include <botcraft/AI/Tasks/BaseTasks.hpp>
#include <botcraft/AI/Tasks/PathfindingTask.hpp>
#include <botcraft/Game/Vector3.hpp>
#include "WorldEaterClient.hpp"
using namespace Botcraft;
WorldEaterClient::WorldEaterClient(const std::string& trigger_word, const bool use_renderer_) : SimpleBehaviourClient(use_renderer_)
{
word = trigger_word;
}
WorldEaterClient::~WorldEaterClient()
{
}
#if PROTOCOL_VERSION < 759 /* < 1.19 */
void WorldEaterClient::Handle(ProtocolCraft::ClientboundChatPacket& msg)
{
ManagersClient::Handle(msg);
ProcessChatMsg(msg.GetMessage().GetText());
}
#else
void WorldEaterClient::Handle(ProtocolCraft::ClientboundPlayerChatPacket& msg)
{
ManagersClient::Handle(msg);
#if PROTOCOL_VERSION == 759 /* 1.19 */
ProcessChatMsg(msg.GetSignedContent().GetText());
#elif PROTOCOL_VERSION == 760 /* 1.19.1/2 */
ProcessChatMsg(msg.GetMessage_().GetSignedBody().GetContent().GetPlain());
#else
ProcessChatMsg(msg.GetBody().GetContent());
#endif
}
void WorldEaterClient::Handle(ProtocolCraft::ClientboundSystemChatPacket& msg)
{
ManagersClient::Handle(msg);
ProcessChatMsg(msg.GetContent().GetText());
}
#endif
void WorldEaterClient::ProcessChatMsg(const std::string& msg)
{
if (msg != word)
{
return;
}
Position out_position;
try
{
out_position = blackboard.Get<Position>("Eater.out_position");
}
catch (const std::exception&)
{
// Probably not initialized yet, do nothing except warn user
LOG_WARNING("out_position not found in the blackboard, can't abort. Is the bot initialized?");
return;
}
const std::string& bot_name = network_manager->GetMyName();
SetBehaviourTree(Builder<SimpleBehaviourClient>("Completion Tree")
.sequence()
.leaf("Go to out pos", GoTo, out_position + Position(0, 1, 0), 0, 0, 0, true, true, 1.0f)
.leaf("Notify", Say, network_manager->GetMyName() + " out")
.leaf("Set should be closed", [](SimpleBehaviourClient& c) { c.SetShouldBeClosed(true); return Status::Success; })
.leaf("Set null tree", [](SimpleBehaviourClient& c) { c.SetBehaviourTree(nullptr); return Status::Success; })
.end());
}
@@ -0,0 +1,152 @@
#include "WorldEaterSubTrees.hpp"
#include "WorldEaterTasks.hpp"
#include <botcraft/AI/Tasks/AllTasks.hpp>
using namespace Botcraft;
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> FullTree()
{
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> go_to_top_ladder = GoToTopLadderTree();
return Builder<SimpleBehaviourClient>("Full Tree")
.sequence()
.tree(InitTree())
.tree(BaseCampResupplyTree())
// Make sure we go near the ladder position first,
// to avoid pathfinding through the other bots
// working area
.tree(go_to_top_ladder)
.tree(MainTree())
.tree(go_to_top_ladder)
// If we exited main tree because of an error, clean inventory before going back to basecamp
.leaf("Clean inventory", CleanInventory)
.tree(CompletionTree())
.end();
}
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> InitTree()
{
return Builder<SimpleBehaviourClient>("Init Tree")
.selector()
.leaf("Is init?", CheckBlackboardBoolData, "Eater.init")
.selector()
.leaf("Init", Init)
// If init failed, stop the behaviour
.leaf("Stop behaviour", [](SimpleBehaviourClient& c) { c.SetBehaviourTree(nullptr); c.Yield(); return Status::Success; })
.end()
.end();
}
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> BaseCampResupplyTree()
{
return Builder<SimpleBehaviourClient>("Basecamp Resupply Tree")
.sequence()
.leaf("Drop items", BaseCampDropItems, false)
.leaf("Pick items", BaseCampPickItems)
.leaf("Has lava", HasItemInInventory, "minecraft:lava_bucket", 1)
.leaf("Has shears", HasToolInInventory, ToolType::Shears, 2)
.leaf("Has hoe", HasToolInInventory, ToolType::Hoe, 2)
.leaf("Has shovel", HasToolInInventory, ToolType::Shovel, 2)
.leaf("Has axe", HasToolInInventory, ToolType::Axe, 2)
.leaf("Has food", HasItemInInventory, "minecraft:golden_carrot", 64)
.leaf("Has ladder", HasItemInInventory, "minecraft:ladder", 64)
.leaf(CopyBlackboardData, "Eater.temp_block", "HasItemInInventory.item_name")
.leaf(SetBlackboardData<int>, "HasItemInInventory.quantity", 5*64)
.leaf("Has temp block", HasItemInInventoryBlackboard)
.leaf("Has pickaxe", HasToolInInventory, ToolType::Pickaxe, 2)
.end();
}
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> MainTree()
{
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> action_loop_tree = ActionLoopTree();
return Builder<SimpleBehaviourClient>("Main Tree")
.repeater("Layer loop", 0).inverter() // repeater(0) + inverter --> repeat until it fails
.sequence("Layer loop body")
.leaf("Validate current layer", ValidateCurrentLayer)
.leaf("Prepare layer", PrepareLayer)
.tree(action_loop_tree)
// CheckActionsDone will exit this loop early if all the
// actions weren't done during the internal loop
.leaf("Check done", CheckActionsDone)
.leaf("Plan layer non walkable", PlanLayerNonWalkable)
.tree(action_loop_tree)
.leaf("Check done", CheckActionsDone)
.leaf("Plan layer fluids", PlanLayerFluids)
.tree(action_loop_tree)
.leaf("Check done", CheckActionsDone)
.leaf("Plan layer blocks", PlanLayerBlocks)
.tree(action_loop_tree)
.leaf("Check done", CheckActionsDone)
.leaf("Move to next layer", MoveToNextLayer)
.end();
}
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> ActionLoopTree()
{
return Builder<SimpleBehaviourClient>("Action Loop Tree")
.sequence()
// Count the number of failed action performed in a row, reset after every successful action
.leaf("Reset failure counter", SetBlackboardData<int>, "Eater.failed_loop", 0)
// Repeat the whole loop until it fails.
// The last leaf makes sure it fails only
// if the internal loop failed three times in
// a row.
.repeater(0).inverter()
.sequence()
// Exit early if action queue is empty
.inverter().leaf("Check done", CheckActionsDone)
.selector()
// Try to perform next action
.sequence("Action loop body")
.selector("Eat")
.inverter().leaf(IsHungry, 15) // Try to optimize golden carrots eating pattern with 15 threshold
.leaf(Eat, "minecraft:golden_carrot", true)
.end()
.selector("Clean inventory")
.inverter().leaf(IsInventoryFull)
.leaf(CleanInventory)
.end()
.inverter().leaf(IsInventoryFull) // If inventory is still full after cleaning, stop loop and go back to basecamp to empty it
.leaf("Get next action", GetNextAction)
.leaf("Execute action", ExecuteAction)
// If the action succeeded, reset failure counter to 0
.leaf("Reset failure counter", SetBlackboardData<int>, "Eater.failed_loop", 0)
.leaf("Pop action", PopAction)
.end()
// If action failed, wait for ~1 sec and increment failure counter
.sequence()
.repeater(100).leaf("Sleep", Yield)
.leaf("Increment failure counter", [](SimpleBehaviourClient& c) { c.GetBlackboard().Set<int>("Eater.failed_loop", c.GetBlackboard().Get<int>("Eater.failed_loop") + 1); return Status::Success; })
.end()
.end()
// If failure counter is equal to 3, break out of the loop
.leaf("Exit loop if 3 failures", [](SimpleBehaviourClient& c) { return c.GetBlackboard().Get<int>("Eater.failed_loop") == 3 ? Status::Failure : Status::Success; })
.end()
.end();
}
std::shared_ptr<BehaviourTree<SimpleBehaviourClient>> CompletionTree()
{
return Builder<SimpleBehaviourClient>("Completion Tree")
.sequence()
.leaf("Check done", CheckBlackboardBoolData, "Eater.done")
.leaf("Drop all items", BaseCampDropItems, true)
.leaf("Go to out pos", [](SimpleBehaviourClient& c) { LOG_INFO("Go to out position" << c.GetBlackboard().Get<Position>("Eater.out_position")); return GoTo(c, c.GetBlackboard().Get<Position>("Eater.out_position") + Position(0, 1, 0), 0, 0); })
.leaf("Notify", Say, "It's over. It's done.")
.leaf("Set should be closed", [](SimpleBehaviourClient& c) { c.SetShouldBeClosed(true); return Status::Success; })
.leaf("Set null tree", [](SimpleBehaviourClient& c) { c.SetBehaviourTree(nullptr); return Status::Success; })
.end();
}
std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> GoToTopLadderTree()
{
return Builder<SimpleBehaviourClient>("Go To Top Ladder Tree")
.sequence()
.leaf(CopyBlackboardData, "Eater.ladder", "GoTo.goal")
.leaf(SetBlackboardData<int>, "GoTo.dist_tolerance", 4)
.leaf(SetBlackboardData<int>, "GoTo.min_end_dist", 1)
.leaf(SetBlackboardData<int>, "GoTo.min_end_dist_xz", 1)
.leaf("Go to ladder", GoToBlackboard)
.end();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,568 @@
#include <botcraft/AI/Tasks/PathfindingTask.hpp>
#include "WorldEaterUtilities.hpp"
using namespace Botcraft;
std::unordered_set<std::string> SplitString(const std::string& s, const char delimiter)
{
std::unordered_set<std::string> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter))
{
tokens.insert(token);
}
return tokens;
}
bool IdentifyBaseCampLayout(SimpleBehaviourClient& client)
{
Blackboard& blackboard = client.GetBlackboard();
const int bot_index = blackboard.Get<int>("Eater.bot_index");
std::unordered_map<std::string, std::string> block_item_mapping = {
{ "minecraft:red_concrete", "drop" },
{ "minecraft:lime_concrete", "out" },
{ "minecraft:orange_shulker_box", "lava_bucket" },
{ "minecraft:magenta_shulker_box", "shears" },
{ "minecraft:purple_shulker_box", "hoe" },
{ "minecraft:blue_shulker_box", "shovel" },
{ "minecraft:light_blue_shulker_box", "axe" },
{ "minecraft:yellow_shulker_box", "golden_carrots" },
{ "minecraft:brown_shulker_box", "ladder" },
{ "minecraft:black_shulker_box", "temp_block" },
{ "minecraft:cyan_shulker_box", "pickaxe" }
};
std::unordered_map<std::string, std::vector<Position>> found_blocks;
for (const auto& [k, v] : block_item_mapping)
{
found_blocks[k] = {};
}
std::shared_ptr<World> world = client.GetWorld();
Position current_position;
// Loop through all loaded blocks to find the one we are looking for
{
auto all_chunks = world->GetChunks();
for (const auto& [coords, chunk] : *all_chunks)
{
for (int y = chunk.GetMinY(); y < chunk.GetMinY() + chunk.GetHeight(); ++y)
{
current_position.y = y;
for (int z = 0; z < CHUNK_WIDTH; ++z)
{
current_position.z = z;
for (int x = 0; x < CHUNK_WIDTH; ++x)
{
current_position.x = x;
const Blockstate* blockstate = chunk.GetBlock(current_position);
if (blockstate == nullptr)
{
continue;
}
if (const auto it = found_blocks.find(blockstate->GetName()); it != found_blocks.end())
{
const Position world_coordinates(
coords.first * CHUNK_WIDTH + x,
y,
coords.second * CHUNK_WIDTH + z
);
it->second.push_back(world_coordinates);
if (bot_index == 0)
{
LOG_INFO(blockstate->GetName() << "(" << block_item_mapping.at(blockstate->GetName()) << ") found at " << world_coordinates);
}
}
}
}
}
}
}
// Sort all positions in a consistent way for all bots
for (auto& [_, v] : found_blocks)
{
std::sort(v.begin(), v.end(), [&](const Position& a, const Position& b)
{
return a.x < b.x ||
(a.x == b.x && a.y < b.y) ||
(a.x == b.x && a.y == b.y && a.z < b.z);
});
}
// Assign dedicated positions for each bot based on index
for (const auto& [k, v] : block_item_mapping)
{
const std::vector<Position>& positions = found_blocks.at(k);
if (positions.size() == 0)
{
if (bot_index == 0)
{
LOG_ERROR("Can't find any " << k << ". I can't work without " << v);
}
return false;
}
blackboard.Set<Position>("Eater." + v + "_position", positions[bot_index % positions.size()]);
}
return true;
}
std::unordered_set<Position> CollectBlocks(const std::shared_ptr<World> world, const Position& start, const Position& end, const int layer, const bool fluids, const bool solids)
{
std::unordered_set<Position> output;
Position p(0, layer, 0);
for (int x = start.x; x <= end.x; ++x)
{
p.x = x;
for (int z = start.z; z <= end.z; ++z)
{
p.z = z;
const Blockstate* blockstate = world->GetBlock(p);
if (blockstate == nullptr || blockstate->IsAir())
{
continue;
}
if (// if fluid, we are not interested in the corner blocks as they can't "leak" into the working area
(fluids && (blockstate->IsFluid() || blockstate->IsWaterlogged()) && ((x != start.x && x != end.x) || (z != start.z && z != end.z))) ||
// if solid
(solids && blockstate->IsSolid()) ||
// not fluid not solid and non walkable
(!fluids && !solids && !blockstate->IsWaterlogged() && (!blockstate->IsFluid() || blockstate->IsHazardous()) && (!blockstate->IsSolid() || blockstate->IsHazardous())) ||
// not fluid not solid and gravity hazard
(!fluids && !solids && CouldFallIfUpdated(blockstate->GetName())))
{
output.insert(Position(x, layer, z));
}
}
}
return output;
}
std::vector<std::unordered_set<Position>> GroupBlocksInComponents(const Position& start, const Position& end, const Position& start_point, const BehaviourClient& client, const std::unordered_set<Position>& positions, std::unordered_map<Position, std::unordered_set<Position>>* additional_neighbours)
{
std::vector<std::unordered_set<Position>> components;
std::unordered_map<Position, int> components_index;
for (const Position& b : positions)
{
components_index[b] = -1;
}
// For all block elements
for (const auto& p : positions)
{
// If we already set a component to this position, skip it
if (components_index[p] != -1)
{
continue;
}
// Otherwise add all neighbours we can find to the current component
std::unordered_set<Position> current_component;
std::unordered_set<Position> neighbours = { p };
while (neighbours.size() > 0)
{
// Take first element in current neighbours
const Position current_pos = *neighbours.begin();
neighbours.erase(current_pos);
// This neighbour is already in a component
if (components_index[current_pos] != -1)
{
continue;
}
current_component.insert(current_pos);
components_index[current_pos] = components.size();
if (current_pos.x > start.x)
{
const Position west = current_pos + Position(-1, 0, 0);
if (const auto it = components_index.find(west); it != components_index.end() && it->second == -1)
{
neighbours.insert(west);
}
}
if (current_pos.x < end.x)
{
const Position east = current_pos + Position(1, 0, 0);
if (const auto it = components_index.find(east); it != components_index.end() && it->second == -1)
{
neighbours.insert(east);
}
}
if (current_pos.z > start.z)
{
const Position north = current_pos + Position(0, 0, -1);
if (const auto it = components_index.find(north); it != components_index.end() && it->second == -1)
{
neighbours.insert(north);
}
}
if (current_pos.z < end.z)
{
const Position south = current_pos + Position(0, 0, 1);
if (const auto it = components_index.find(south); it != components_index.end() && it->second == -1)
{
neighbours.insert(south);
}
}
}
if (current_component.size() == 0)
{
continue;
}
// If additional neighbours are not asked, don't try to merge
// components using pathfinding
if (additional_neighbours == nullptr)
{
components.push_back(current_component);
continue;
}
// Check if we can pathfind from one of the previous components
// to this one and merge them if we can.
// We do not allow the 1-wide gaps during the pathfinding search because of the
// case illustrated below:
// __A__ __B__
// | | | |
// __C__
// | |
// when standing on and having to break B, the bot would pathfind to the nearest block
// which would be C (because jumping has a penalty in the pathfinding search). So it
// goes down to C, breaks B and that's it. It can't get back on top of A to continue the
// current layer. Disabling 1-wide jumps solves this by making A and B always adjacent,
// i.e. closer to each other than C that is at a 2 blocks distance.
bool merged = false;
for (size_t i = 0; i < components.size(); ++i)
{
const Position pathfinding_start = *components[i].begin() + Position(0, 1, 0);
const Position pathfinding_end = *current_component.begin() + Position(0, 1, 0);
const std::vector<Position> path = FindPath(client, pathfinding_start, pathfinding_end, 0, 0, false, false);
const std::vector<Position> reversed_path = FindPath(client, pathfinding_end, pathfinding_start, 0, 0, false, false);
// If we can pathfind from start to end (both ways to prevent cliff falls that would only allow one-way travel)
if (path.back() == pathfinding_end && reversed_path.back() == pathfinding_start)
{
merged = true;
// Add link between the two components as non adjacent neighbours
Position path_block_component1 = pathfinding_start + Position(0, -1, 0);
for (size_t j = 0; j < path.size(); ++j)
{
// If the pathfinding crosses the boundaries of the work area anywhere else than the ladder,
// cancel the merging of the components
if ((path[j].x < start.x || path[j].x > end.x || path[j].z < start.z || path[j].z > end.z) &&
path[j].x != start_point.x && path[j].z != start_point.z)
{
merged = false;
break;
}
if (additional_neighbours != nullptr)
{
// If component index of the current path position is equal to the one currently compared to (i)
auto it = components_index.find(path[j] + Position(0, -1, 0));
if (it != components_index.end() && it->second == i)
{
path_block_component1 = path[j] + Position(0, -1, 0);
}
}
}
Position path_block_component2 = pathfinding_end + Position(0, -1, 0);
for (size_t j = 0; j < reversed_path.size(); ++j)
{
// If the pathfinding crosses the boundaries of the work area anywhere else than the ladder,
// cancel the merging of the components
if ((reversed_path[j].x < start.x || reversed_path[j].x > end.x || reversed_path[j].z < start.z || reversed_path[j].z > end.z) &&
reversed_path[j].x != start_point.x && reversed_path[j].z != start_point.z)
{
merged = false;
break;
}
if (additional_neighbours != nullptr)
{
// If component index of the current path position is equal to the current one to add (components.size())
auto it = components_index.find(reversed_path[j] + Position(0, -1, 0));
if (it != components_index.end() && it->second == components.size())
{
path_block_component2 = reversed_path[j] + Position(0, -1, 0);
}
}
}
if (merged)
{
for (const Position& p : current_component)
{
components_index[p] = i;
}
components[i].insert(current_component.begin(), current_component.end());
if (additional_neighbours != nullptr)
{
(*additional_neighbours)[path_block_component1].insert(path_block_component2);
(*additional_neighbours)[path_block_component2].insert(path_block_component1);
}
}
}
}
if (!merged)
{
components.push_back(current_component);
}
}
return components;
}
std::vector<Position> GetBlocksToAdd(const std::vector<std::unordered_set<Position>>& components, const Position& start_point, const Position& min_bound, const Position& max_bound)
{
if (components.empty())
{
return {};
}
std::unordered_set<int> components_already_processed = { -1 };
std::unordered_set<int> components_to_process;
// Add all components to "to_process" vector
for (int i = 0; i < components.size(); ++i)
{
components_to_process.insert(i);
}
std::vector<Position> output;
output.reserve(components_to_process.size()); // rough estimate of ~1:1 ratio block present/block to add
while (components_to_process.size() > 0)
{
float min_dist = std::numeric_limits<float>::max();
int argmin_component_idx = -1;
Position from;
Position to;
// Look for the "closest" remaining component
// from the one we already have
for (const int c1 : components_already_processed)
{
for (const int c2 : components_to_process)
{
for (const Position& p1 : c1 != -1 ? components[c1] : std::unordered_set<Position>{ start_point })
{
for (const Position& p2 : components[c2])
{
const float dist = std::abs(p1.x - p2.x) + std::abs(p1.z - p2.z);
if (dist < min_dist)
{
min_dist = dist;
argmin_component_idx = c2;
from = p1;
to = p2;
}
}
}
}
}
// Straight line to get back into the working area
// as quickly as possible
while (from != to && (
from.x < min_bound.x ||
from.x > max_bound.x ||
from.z < min_bound.z ||
from.z > max_bound.z))
{
if (from.x < min_bound.x)
{
from.x += 1;
}
else if (from.z < min_bound.z)
{
from.z += 1;
}
else if (from.x > max_bound.x)
{
from.x -= 1;
}
else if (from.z > max_bound.z)
{
from.z -= 1;
}
if (from != to)
{
output.push_back(from);
}
}
// Dumb 2D staircase between from and to
while (from != to)
{
if (std::abs(to.x - from.x) > std::abs(to.z - from.z))
{
from.x += from.x < to.x ? 1 : -1;
}
else
{
from.z += from.z < to.z ? 1 : -1;
}
if (from != to)
{
output.push_back(from);
}
}
components_already_processed.insert(argmin_component_idx);
// Remove start point after first time
components_already_processed.erase(-1);
components_to_process.erase(argmin_component_idx);
}
return output;
}
std::unordered_set<Position> FlattenComponentsAndAdditionalBlocks(const std::vector<std::unordered_set<Position>>& components, const std::vector<Position>& additional_blocks)
{
std::unordered_set<Position> output;
for (const std::unordered_set<Position>& v : components)
{
output.insert(v.begin(), v.end());
}
output.insert(additional_blocks.begin(), additional_blocks.end());
return output;
}
std::vector<Position> ComputeBlockOrder(std::unordered_set<Position> blocks, const Position& start_block, const Direction orientation, const std::unordered_map<Position, std::unordered_set<Position>>& additional_neighbours)
{
struct SortingNode
{
Position pos;
std::vector<SortingNode> children;
};
// Should not happen as at least start_block should be in blocks
if (blocks.size() == 0)
{
return { };
}
blocks.erase(start_block);
SortingNode root{ start_block };
std::queue<SortingNode*> to_process;
to_process.push(&root);
std::array<Position, 4> potential_neighbours;
if (orientation == Direction::East || orientation == Direction::West)
{
potential_neighbours = {
Position(-1, 0, 0), // west
Position(1, 0, 0), // east
Position(0, 0, -1), // north
Position(0, 0, 1) // south
};
}
else
{
potential_neighbours = {
Position(0, 0, -1), // north
Position(0, 0, 1), // south
Position(-1, 0, 0), // west
Position(1, 0, 0) // east
};
}
while (blocks.size() > 0)
{
if (to_process.empty())
{
LOG_ERROR("to_process queue empty while ordering blocks to process. This should not happen (?)");
return {};
}
SortingNode* current_node = to_process.front();
// If there are some additional neighbours to this node, check them and the direct neighbours
if (const auto it = additional_neighbours.find(current_node->pos); it != additional_neighbours.end())
{
for (const Position& potential_neighbour : it->second)
{
if (blocks.find(potential_neighbour) != blocks.end())
{
SortingNode neighbour;
neighbour.pos = potential_neighbour;
current_node->children.emplace_back(neighbour);
}
}
for (const Position& offset : potential_neighbours)
{
const Position potential_neighbour = current_node->pos + offset;
// Only add it if it's in blocks AND we didn't already added it as a potential neighbour
if (blocks.find(potential_neighbour) != blocks.end() && it->second.find(potential_neighbour) == it->second.end())
{
SortingNode neighbour;
neighbour.pos = potential_neighbour;
current_node->children.emplace_back(neighbour);
}
}
}
// Else only check the direct neighbours
else
{
for (const Position& offset : potential_neighbours)
{
const Position potential_neighbour = current_node->pos + offset;
if (blocks.find(potential_neighbour) != blocks.end())
{
SortingNode neighbour;
neighbour.pos = potential_neighbour;
current_node->children.emplace_back(neighbour);
}
}
}
for (size_t i = 0; i < current_node->children.size(); ++i)
{
to_process.push(current_node->children.data() + i);
blocks.erase(current_node->children[i].pos);
}
to_process.pop();
}
// Recursive function to add all children, then current node
// This allows to be sure that we will never "cut the branch
// we are sitting on" preventing getting back to start_point
const std::function<std::vector<Position>(const SortingNode& n)> get_sorted_positions =
[&get_sorted_positions](const SortingNode& n) -> std::vector<Position>
{
std::vector<Position> output;
for (const SortingNode& c : n.children)
{
const std::vector<Position> children_sorted = get_sorted_positions(c);
output.insert(output.end(), children_sorted.begin(), children_sorted.end());
}
output.push_back(n.pos);
return output;
};
return get_sorted_positions(root);
}
bool CouldFallIfUpdated(const std::string& block_name)
{
const static std::unordered_set<std::string> gravity_blocks = {
"minecraft:sand",
"minecraft:gravel",
"minecraft:red_sand",
#if PROTOCOL_VERSION > 762 /* > 1.19.4 */
"minecraft:suspicious_sand",
"minecraft:suspicious_gravel",
#endif
};
return gravity_blocks.find(block_name) != gravity_blocks.end();
}
@@ -0,0 +1,283 @@
#include <iostream>
#include <string>
#include <botcraft/Game/Vector3.hpp>
#include <botcraft/Game/World/World.hpp>
#include <botcraft/AI/SimpleBehaviourClient.hpp>
#include <botcraft/Utilities/Logger.hpp>
#include <botcraft/Utilities/SleepUtilities.hpp>
#include "WorldEaterSubTrees.hpp"
#include "WorldEaterClient.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--numbot\tNumber of parallel bot to start, default: 16\n"
<< "\t--numworld\tNumber of shared world used by bots, less worlds saves RAM, but can be slower if shared between too many bots, default: 4\n"
<< "\t--start\t3 ints, offset for the first block, default: -64 -59 832\n"
<< "\t--end\t3 ints, offset for the last block, default: 63 80 959\n"
<< "\t--tempblock\tname of the temp block, must be a full solid block, default: minecraft:basalt\n"
<< "\t--spared\tcomma-separated list of blocks you don't want the bots to break, default: minecraft:spawner,minecraft:torch\n"
<< "\t--collected\tcomma-separated list of items you want the bot to store (be careful to use item names instead of blocks if tools don't have silk touch), default: minecraft:diamond_ore,minecraft:deepslate_diamond_ore\n"
<< "\t--stopword\tif someone says this word in chat, all bots will go back to basecamp and stop working. Use it if you need to pause the process to be able to resume it, default:banana\n"
<< std::endl;
}
struct Args
{
bool help = false;
std::string address = "127.0.0.1:25565";
int num_bot = 16;
int num_world = 4;
Botcraft::Position start = Botcraft::Position(-64, -59, 832);
Botcraft::Position end = Botcraft::Position(63, 80, 959);
std::string temp_block = "minecraft:basalt";
std::string spared_blocks = "minecraft:spawner,minecraft:torch";
std::string collected_blocks = "minecraft:diamond_ore,minecraft:deepslate_diamond_ore";
std::string stopword = "banana";
int return_code = 0;
};
Args ParseCommandLine(int argc, char* argv[]);
int main(int argc, char* argv[])
{
try
{
// Init logging, log everything >= Info, to console and file
Botcraft::Logger::GetInstance().SetLogLevel(Botcraft::LogLevel::Info);
Botcraft::Logger::GetInstance().SetFilename("world-eater.logs");
// Add a name to this thread for logging
Botcraft::Logger::GetInstance().RegisterThread("main");
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;
}
}
const std::vector<std::string> base_names = {
"BotAuFeu", "Botager", "Botiron", "BotEnTouche",
"BotDeVin", "BotAuxRoses", "BotronMinet", "Botmobile",
"Botman", "Botentiel", "BotDog", "Botrefois",
"Botomate", "Botaku", "Botaubus", "Bothentique"
};
const std::shared_ptr<Botcraft::BehaviourTree<Botcraft::SimpleBehaviourClient>> eater_behaviour_tree = FullTree();
std::vector<std::shared_ptr<Botcraft::World> > shared_worlds(args.num_world);
for (int i = 0; i < args.num_world; ++i)
{
shared_worlds[i] = std::make_shared<Botcraft::World>(true);
}
std::vector<std::string> names(args.num_bot);
std::vector<std::shared_ptr<WorldEaterClient> > clients(args.num_bot);
for (int i = 0; i < args.num_bot; ++i)
{
// Get a unique name for this bot
names[i] = i < base_names.size() ? base_names[i] : (base_names[i % base_names.size()] + std::to_string(i / base_names.size()));
// Create the bot client and connect to the server
clients[i] = std::make_shared<WorldEaterClient>(args.stopword, false);
clients[i]->SetSharedWorld(shared_worlds[i % args.num_world]);
clients[i]->SetAutoRespawn(true);
clients[i]->Connect(args.address, names[i], false);
// Start behaviour thread and set active tree
clients[i]->StartBehaviour();
clients[i]->SetBehaviourTree(eater_behaviour_tree, {
{ "Eater.bot_index" , i },
{ "Eater.num_bot", args.num_bot },
{ "Eater.start_block", args.start },
{ "Eater.end_block", args.end },
{ "Eater.temp_block", args.temp_block },
{ "Eater.spared_blocks", args.spared_blocks },
{ "Eater.collected_blocks", args.collected_blocks }
});
// Wait 2 seconds between each bot to avoid having too many bots loading all the chunks at the same time
Botcraft::Utilities::SleepFor(std::chrono::seconds(2));
}
std::vector<std::thread> behaviours_threads(args.num_bot);
for (int i = 0; i < args.num_bot; ++i)
{
behaviours_threads[i] = std::thread(&Botcraft::SimpleBehaviourClient::RunBehaviourUntilClosed, clients[i]);
// Start all the behaviours with a 2 seconds interval
Botcraft::Utilities::SleepFor(std::chrono::seconds(2));
}
// Wait for all the bots to disconnect (meaning the job is done if everything worked properly)
for (int i = 0; i < args.num_bot; ++i)
{
behaviours_threads[i].join();
}
return 0;
}
catch (std::exception& e)
{
LOG_FATAL("Exception: " << e.what());
return 1;
}
catch (...)
{
LOG_FATAL("Unknown exception");
return 2;
}
}
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 == "--numbot")
{
if (i + 1 < argc)
{
args.num_bot = std::stoi(argv[++i]);
}
else
{
LOG_FATAL("--numbot requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--numworld")
{
if (i + 1 < argc)
{
args.num_world = std::stoi(argv[++i]);
}
else
{
LOG_FATAL("--numworld requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--start")
{
if (i + 3 < argc)
{
int x = std::stoi(argv[++i]);
int y = std::stoi(argv[++i]);
int z = std::stoi(argv[++i]);
args.start = Botcraft::Position(x, y, z);
}
else
{
LOG_FATAL("--start requires 3 arguments");
args.return_code = 1;
return args;
}
}
else if (arg == "--end")
{
if (i + 3 < argc)
{
int x = std::stoi(argv[++i]);
int y = std::stoi(argv[++i]);
int z = std::stoi(argv[++i]);
args.end = Botcraft::Position(x, y, z);
}
else
{
LOG_FATAL("--end requires 3 arguments");
args.return_code = 1;
return args;
}
}
else if (arg == "--tempblock")
{
if (i + 1 < argc)
{
args.temp_block = argv[++i];
}
else
{
LOG_FATAL("--tempblock requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--spared")
{
if (i + 1 < argc)
{
args.spared_blocks = argv[++i];
}
else
{
LOG_FATAL("--spared requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--collected")
{
if (i + 1 < argc)
{
args.collected_blocks = argv[++i];
}
else
{
LOG_FATAL("--collected requires an argument");
args.return_code = 1;
return args;
}
}
else if (arg == "--stopword")
{
if (i + 1 < argc)
{
args.stopword = argv[++i];
}
else
{
LOG_FATAL("--stopword requires an argument");
args.return_code = 1;
return args;
}
}
}
return args;
}
+36
View File
@@ -0,0 +1,36 @@
# Define a macro that can be reused to quickly setup an Example project
# without all the boilerplate code
macro(add_example include_folders source_files)
add_executable(${PROJECT_NAME} ${source_files})
target_include_directories(${PROJECT_NAME} PUBLIC ${include_folders})
target_link_libraries(${PROJECT_NAME} botcraft)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17)
set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Examples)
set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX "_d")
set_target_properties(${PROJECT_NAME} PROPERTIES RELWITHDEBINFO_POSTFIX "_rd")
if(MSVC)
# To avoid having folder for each configuration when building with Visual
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG "${BOTCRAFT_OUTPUT_DIR}/bin")
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE "${BOTCRAFT_OUTPUT_DIR}/bin")
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${BOTCRAFT_OUTPUT_DIR}/bin")
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${BOTCRAFT_OUTPUT_DIR}/bin")
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${BOTCRAFT_OUTPUT_DIR}/bin")
else()
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${BOTCRAFT_OUTPUT_DIR}/bin")
endif(MSVC)
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endmacro()
add_subdirectory(0_HelloWorld)
add_subdirectory(1_UserControlledExample)
add_subdirectory(2_ChatCommandExample)
add_subdirectory(3_SimpleAFKExample)
add_subdirectory(4_MapCreatorExample)
add_subdirectory(5_MobHitterExample)
if (PROTOCOL_VERSION STRGREATER "470") # 1.14+
add_subdirectory(6_DispenserFarmExample)
endif()
add_subdirectory(7_WorldEaterExample)