mirror of
https://github.com/Astatin3/meteorbot-old.git
synced 2026-06-09 00:28:06 -06:00
Initial commit
This commit is contained in:
@@ -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}")
|
||||
+31
@@ -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;
|
||||
};
|
||||
+60
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user