mirror of
https://github.com/Astatin3/meteorbot-old.git
synced 2026-06-09 08:38:07 -06:00
Initial commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
project(botcraft_online_tests)
|
||||
|
||||
set(HDR_FILES
|
||||
include/MinecraftServer.hpp
|
||||
include/TestManager.hpp
|
||||
include/Utils.hpp
|
||||
)
|
||||
|
||||
set(SRC_FILES
|
||||
src/main.cpp
|
||||
|
||||
src/MinecraftServer.cpp
|
||||
src/TestManager.cpp
|
||||
|
||||
src/tests/base_tasks.cpp
|
||||
src/tests/dig.cpp
|
||||
src/tests/entities.cpp
|
||||
src/tests/inventory.cpp
|
||||
src/tests/pathfinding.cpp
|
||||
src/tests/physics.cpp
|
||||
src/tests/self-tests.cpp
|
||||
src/tests/world.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${HDR_FILES} ${SRC_FILES})
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Catch2::Catch2)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE botcraft)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE subprocess)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
get_filename_component(BOTCRAFT_ROOT_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE BASE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src/tests")
|
||||
if(BOTCRAFT_COMPRESSION)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_COMPRESSION=1)
|
||||
endif(BOTCRAFT_COMPRESSION)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Tests)
|
||||
|
||||
# To have a nice files structure in Visual Studio
|
||||
if(MSVC)
|
||||
foreach(source IN LISTS HDR_FILES)
|
||||
get_filename_component(source_path_header "${source}" PATH)
|
||||
string(REPLACE "include" "Header Files" source_path_header "${source_path_header}")
|
||||
string(REPLACE "/" "\\" source_path_msvc "${source_path_header}")
|
||||
source_group("${source_path_msvc}" FILES "${source}")
|
||||
endforeach()
|
||||
|
||||
foreach(source IN LISTS SRC_FILES)
|
||||
get_filename_component(source_path "${source}" PATH)
|
||||
string(REPLACE "src" "Source Files" source_path "${source_path}")
|
||||
string(REPLACE "/" "\\" source_path_msvc "${source_path}")
|
||||
source_group("${source_path_msvc}" FILES "${source}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
|
||||
# Output the test executable next to the examples and library files
|
||||
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_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_DEBUG "${BOTCRAFT_OUTPUT_DIR}/bin")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_RELEASE "${BOTCRAFT_OUTPUT_DIR}/bin")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO "${BOTCRAFT_OUTPUT_DIR}/bin")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL "${BOTCRAFT_OUTPUT_DIR}/bin")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${BOTCRAFT_OUTPUT_DIR}/lib")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${BOTCRAFT_OUTPUT_DIR}/lib")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO "${BOTCRAFT_OUTPUT_DIR}/lib")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL "${BOTCRAFT_OUTPUT_DIR}/lib")
|
||||
|
||||
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")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${BOTCRAFT_OUTPUT_DIR}/bin")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${BOTCRAFT_OUTPUT_DIR}/lib")
|
||||
endif(MSVC)
|
||||
|
||||
# Manual add_test instead of catch_discover_tests because otherwise each test is launched by a separate process
|
||||
# i.e. a separate server instance. And we want everything to happen on the same server.
|
||||
add_test(
|
||||
NAME ${PROJECT_NAME}
|
||||
COMMAND $<TARGET_FILE:${PROJECT_NAME}>
|
||||
WORKING_DIRECTORY "${BOTCRAFT_OUTPUT_DIR}/bin"
|
||||
)
|
||||
|
||||
# Copy runtime files to bin output folder
|
||||
if(NOT IS_DIRECTORY "${BOTCRAFT_OUTPUT_DIR}/bin/test_server_files/runtime")
|
||||
message(STATUS "Copying runtime online test files to bin folder...")
|
||||
endif()
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/runtime" DESTINATION "${BOTCRAFT_OUTPUT_DIR}/bin/test_server_files")
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
struct subprocess_s;
|
||||
|
||||
struct MinecraftServerOptions
|
||||
{
|
||||
std::string argv0 = "";
|
||||
int view_distance = 5;
|
||||
};
|
||||
|
||||
/// @brief Singleton wrapper around a Minecraft server subprocess instance.
|
||||
/// Built to be used in a single-threaded way (to follow catch2 requirements).
|
||||
class MinecraftServer
|
||||
{
|
||||
private:
|
||||
MinecraftServer();
|
||||
~MinecraftServer();
|
||||
MinecraftServer(const MinecraftServer&) = delete;
|
||||
MinecraftServer(MinecraftServer&&) = delete;
|
||||
MinecraftServer& operator=(const MinecraftServer&) = delete;
|
||||
|
||||
public:
|
||||
static MinecraftServerOptions options;
|
||||
static MinecraftServer& GetInstance();
|
||||
|
||||
void Initialize();
|
||||
|
||||
/// @brief Wait for the subprocess to write a line to stdout or stderr. Thread safe
|
||||
/// @param timeout_ms If != 0 and no line received after timeout_ms, will throw an exception
|
||||
/// @return The received line content
|
||||
std::string WaitLine(const long long int timeout_ms = 0);
|
||||
|
||||
/// @brief Wait for the subprocess to write a line matching regex to stdout or stderr.
|
||||
/// @param regex A regex pattern of the line to match
|
||||
/// @param timeout_ms If != 0 and no matching line received after timeout_ms, will throw an exception
|
||||
/// @return First element is the full line, other are all the captured groups from the regex
|
||||
std::vector<std::string> WaitLine(const std::string& regex, const long long int timeout_ms = 0);
|
||||
|
||||
/// @brief Write a line to subprocess stdin. Not thread-safe.
|
||||
/// @param input Line to write
|
||||
void SendLine(const std::string& input);
|
||||
|
||||
/// @brief Path to the folder holding the structure files
|
||||
/// @return The path in which the server loads the structures
|
||||
std::filesystem::path GetStructurePath() const;
|
||||
|
||||
private:
|
||||
/// @brief Terminate the subprocess
|
||||
void Kill();
|
||||
/// @brief Internal function used to retrieve stdout and stderr
|
||||
void InternalThreadRead();
|
||||
/// @brief Prepare a folder to host a server
|
||||
void InitServerFolder(const std::filesystem::path& path);
|
||||
/// @brief Set a gamerule value on the server
|
||||
void SetGamerule(const std::string& gamerule, const std::string& value);
|
||||
/// @brief Set all non default gamerules on the server
|
||||
void InitServerGamerules();
|
||||
|
||||
private:
|
||||
/// @brief Thread running the stdout reading loop
|
||||
std::thread read_thread;
|
||||
/// @brief Internal mutex for the std::queue lines
|
||||
std::mutex internal_read_mutex;
|
||||
/// @brief Queue of lines generated by the subprocess
|
||||
std::queue<std::string> lines;
|
||||
|
||||
/// @brief Server subprocess handle
|
||||
std::unique_ptr<subprocess_s> subprocess;
|
||||
|
||||
bool running;
|
||||
|
||||
std::filesystem::path server_path;
|
||||
};
|
||||
@@ -0,0 +1,236 @@
|
||||
#pragma once
|
||||
|
||||
#include <catch2/reporters/catch_reporter_event_listener.hpp>
|
||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include <botcraft/AI/BehaviourClient.hpp>
|
||||
#include <botcraft/Game/Vector3.hpp>
|
||||
#include <botcraft/Game/Enums.hpp>
|
||||
#include <botcraft/Game/ManagersClient.hpp>
|
||||
#include <botcraft/Game/World/World.hpp>
|
||||
#include <botcraft/Utilities/SleepUtilities.hpp>
|
||||
|
||||
#include "MinecraftServer.hpp"
|
||||
|
||||
|
||||
/// @brief Singleton class that organizes the layout of the tests
|
||||
/// in the world and sets them up.
|
||||
class TestManager
|
||||
{
|
||||
private:
|
||||
TestManager();
|
||||
~TestManager();
|
||||
TestManager(const TestManager&) = delete;
|
||||
TestManager(TestManager&&) = delete;
|
||||
TestManager& operator=(const TestManager&) = delete;
|
||||
|
||||
/// @brief Spacing between tests
|
||||
static constexpr int spacing_x = 3;
|
||||
/// @brief Spacing between sections
|
||||
static constexpr int spacing_z = 3;
|
||||
|
||||
public:
|
||||
static TestManager& GetInstance();
|
||||
|
||||
/// @brief current_offset getter
|
||||
/// @return The offset in the world of the currently running section
|
||||
const Botcraft::Position& GetCurrentOffset() const;
|
||||
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
void SetBlock(const std::string& name, const Botcraft::Position& pos, const std::map<std::string, std::string>& blockstates = {}, const std::map<std::string, std::string>& metadata = {}, const bool escape_metadata = true) const;
|
||||
#else
|
||||
void SetBlock(const std::string& name, const Botcraft::Position& pos, const int block_variant = 0, const std::map<std::string, std::string>& metadata = {}, const bool escape_metadata = true) const;
|
||||
#endif
|
||||
|
||||
/// @brief Create a book with given content at pos
|
||||
/// @param pos Position of the item frame/lectern
|
||||
/// @param pages Content of the pages of the book
|
||||
/// @param facing Orientation of the item frame/lectern (book is toward this direction)
|
||||
/// @param title Title of the book
|
||||
/// @param author Author of the book
|
||||
/// @param description Description of the book (minecraft tooltip)
|
||||
void CreateBook(const Botcraft::Position& pos, const std::vector<std::string>& pages, const std::string& facing = "north", const std::string& title = "", const std::string& author = "", const std::vector<std::string>& description = {});
|
||||
|
||||
void Teleport(const std::string& name, const Botcraft::Vector3<double>& pos, const float yaw = 0.0f, const float pitch = 0.0f) const;
|
||||
|
||||
template<class ClientType = Botcraft::ManagersClient,
|
||||
std::enable_if_t<std::is_base_of_v<Botcraft::ConnectionClient, ClientType>, bool> = true>
|
||||
std::unique_ptr<ClientType> GetBot(std::string& botname, int& id, Botcraft::Vector3<double>& pos, const Botcraft::GameType gamemode = Botcraft::GameType::Survival)
|
||||
{
|
||||
std::unique_ptr<ClientType> client;
|
||||
if constexpr (std::is_same_v<ClientType, Botcraft::ConnectionClient>)
|
||||
{
|
||||
client = std::make_unique<ClientType>();
|
||||
}
|
||||
else
|
||||
{
|
||||
client = std::make_unique<ClientType>(false);
|
||||
}
|
||||
|
||||
botname = "botcraft_" + std::to_string(bot_index++);
|
||||
client->Connect("127.0.0.1:25565", botname);
|
||||
|
||||
std::vector<std::string> captured = MinecraftServer::GetInstance().WaitLine(".*?" + botname + ".*? logged in with entity id (\\d+) at \\(([\\d.,]+), ([\\d.,]+), ([\\d.,]+)\\).*", 5000);
|
||||
id = std::stoi(captured[1]);
|
||||
pos = Botcraft::Vector3(std::stod(captured[2]), std::stod(captured[3]), std::stod(captured[4]));
|
||||
|
||||
MinecraftServer::GetInstance().WaitLine(".*?" + botname + " joined the game.*", 5000);
|
||||
|
||||
if (gamemode != Botcraft::GameType::Creative)
|
||||
{
|
||||
SetGameMode(botname, gamemode);
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<ClientType, Botcraft::ConnectionClient>)
|
||||
{
|
||||
return client;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this is not a ConnectionClient, wait for a good amount of chunks to be loaded
|
||||
const int num_chunk_to_load = (MinecraftServer::options.view_distance + 1) * (MinecraftServer::options.view_distance + 1);
|
||||
if (!Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
std::shared_ptr<Botcraft::World> world = client->GetWorld();
|
||||
// If the client has not received a ClientboundGameProfilePacket yet world is still nullptr
|
||||
if (world != nullptr && world->GetChunks()->size() >= num_chunk_to_load)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, 10000))
|
||||
{
|
||||
throw std::runtime_error(botname + " took too long to load world");
|
||||
}
|
||||
|
||||
if constexpr (std::is_base_of_v<Botcraft::BehaviourClient, ClientType>)
|
||||
{
|
||||
client->StartBehaviour();
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
template<class ClientType = Botcraft::ManagersClient,
|
||||
std::enable_if_t<std::is_base_of_v<Botcraft::ConnectionClient, ClientType>, bool> = true>
|
||||
std::unique_ptr<ClientType> GetBot(std::string& botname, const Botcraft::GameType gamemode = Botcraft::GameType::Survival)
|
||||
{
|
||||
int id;
|
||||
Botcraft::Vector3<double> pos;
|
||||
return GetBot<ClientType>(botname, id, pos, gamemode);
|
||||
}
|
||||
|
||||
|
||||
template<class ClientType = Botcraft::ManagersClient,
|
||||
std::enable_if_t<std::is_base_of_v<Botcraft::ConnectionClient, ClientType>, bool> = true>
|
||||
std::unique_ptr<ClientType> GetBot(std::string& botname, Botcraft::Vector3<double>& pos, const Botcraft::GameType gamemode = Botcraft::GameType::Survival)
|
||||
{
|
||||
int id;
|
||||
return GetBot<ClientType>(botname, id, pos, gamemode);
|
||||
}
|
||||
|
||||
template<class ClientType = Botcraft::ManagersClient,
|
||||
std::enable_if_t<std::is_base_of_v<Botcraft::ConnectionClient, ClientType>, bool> = true>
|
||||
std::unique_ptr<ClientType> GetBot(const Botcraft::GameType gamemode = Botcraft::GameType::Survival)
|
||||
{
|
||||
std::string botname;
|
||||
int id;
|
||||
Botcraft::Vector3<double> pos;
|
||||
return GetBot<ClientType>(botname, id, pos, gamemode);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class TestSucess
|
||||
{
|
||||
None,
|
||||
Success,
|
||||
Failure,
|
||||
ExpectedFailure
|
||||
};
|
||||
|
||||
/// @brief Read a NBT structure file and extract its size
|
||||
/// @return A X/Y/Z size Vector3
|
||||
Botcraft::Position GetStructureSize(const std::string& filename) const;
|
||||
|
||||
/// @brief Create a TP sign
|
||||
/// @param src Position of the sign.
|
||||
/// @param dst TP destination coordinates
|
||||
/// @param texts A list of strings to display on the sign
|
||||
/// @param facing The direction the sign will face
|
||||
/// @param success A TestSuccess result, will define wood and/or text color depending on minecraft version
|
||||
void CreateTPSign(const Botcraft::Position& src, const Botcraft::Vector3<double>& dst, const std::vector<std::string>& texts, const std::string& facing = "north", const TestSucess success = TestSucess::None) const;
|
||||
|
||||
/// @brief Load a structure block into the world
|
||||
/// @param filename The structure block to load. If it doesn't exist, will use "_default" instead
|
||||
/// @param pos position of the structure block
|
||||
/// @param load_offset offset to load the structure to (w.r.t pos), default to (0,0,0)
|
||||
void LoadStructure(const std::string& filename, const Botcraft::Position& pos, const Botcraft::Position& load_offset = Botcraft::Position()) const;
|
||||
|
||||
/// @brief Make sure a block is loaded on the server by teleporting the chunk_loader
|
||||
/// @param pos The position to load
|
||||
void MakeSureLoaded(const Botcraft::Position& pos) const;
|
||||
|
||||
/// @brief Set a bot gamemode
|
||||
/// @param name Bot name
|
||||
/// @param gamemode Target game mode
|
||||
void SetGameMode(const std::string& name, const Botcraft::GameType gamemode) const;
|
||||
|
||||
|
||||
friend class TestManagerListener;
|
||||
void testRunStarting(Catch::TestRunInfo const& test_run_info);
|
||||
void testCaseStarting(Catch::TestCaseInfo const& test_info);
|
||||
void testCasePartialStarting(Catch::TestCaseInfo const& test_info, uint64_t part_number);
|
||||
void sectionStarting(Catch::SectionInfo const& section_info);
|
||||
void assertionEnded(Catch::AssertionStats const& assertion_stats);
|
||||
void testCasePartialEnded(Catch::TestCaseStats const& test_case_stats, uint64_t part_number);
|
||||
void testCaseEnded(Catch::TestCaseStats const& test_case_stats);
|
||||
void testRunEnded(Catch::TestRunStats const& test_run_info);
|
||||
|
||||
private:
|
||||
/// @brief Offset in the world for current section/test
|
||||
Botcraft::Position current_offset;
|
||||
/// @brief Size of the header structure
|
||||
Botcraft::Position header_size;
|
||||
|
||||
/// @brief Index of the next bot to be created
|
||||
int bot_index;
|
||||
/// @brief Bot used to load chunks before running commands on them
|
||||
std::unique_ptr<Botcraft::ManagersClient> chunk_loader;
|
||||
/// @brief Name of the bot used as chunk loader
|
||||
std::string chunk_loader_name;
|
||||
|
||||
/// @brief Index of the current test in the world
|
||||
int current_test_index;
|
||||
/// @brief Size of the loaded structure for current test
|
||||
Botcraft::Position current_test_size;
|
||||
/// @brief All failed assertions in this test case so far
|
||||
std::vector<std::string> current_test_case_failures;
|
||||
|
||||
/// @brief Store names of all running (potentially) nested sections
|
||||
std::vector<std::string> section_stack;
|
||||
/// @brief Position of the header for current section
|
||||
Botcraft::Position current_header_position;
|
||||
};
|
||||
|
||||
/// @brief Catch2 listener to get test events and pass them to the main singleton
|
||||
/// that can be easily accessed from inside the tests. Maybe there is an easier dedicated
|
||||
/// way to do it without a singleton but couldn't find it.
|
||||
class TestManagerListener : public Catch::EventListenerBase
|
||||
{
|
||||
using Catch::EventListenerBase::EventListenerBase;
|
||||
|
||||
void testRunStarting(Catch::TestRunInfo const& test_run_info) override;
|
||||
void testCaseStarting(Catch::TestCaseInfo const& test_info) override;
|
||||
void testCasePartialStarting(Catch::TestCaseInfo const& test_info, uint64_t part_number) override;
|
||||
void sectionStarting(Catch::SectionInfo const& section_info) override;
|
||||
void assertionEnded(Catch::AssertionStats const& assertion_stats) override;
|
||||
void testCasePartialEnded(Catch::TestCaseStats const& test_case_stats, uint64_t part_number) override;
|
||||
void testCaseEnded(Catch::TestCaseStats const& test_case_stats) override;
|
||||
void testRunEnded(Catch::TestRunStats const& test_run_info) override;
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "TestManager.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <botcraft/AI/TemplatedBehaviourClient.hpp>
|
||||
#include <botcraft/AI/SimpleBehaviourClient.hpp>
|
||||
#include <botcraft/Game/ManagersClient.hpp>
|
||||
#include <botcraft/Game/Vector3.hpp>
|
||||
#include <botcraft/Game/Entities/EntityManager.hpp>
|
||||
#include <botcraft/Game/Entities/LocalPlayer.hpp>
|
||||
|
||||
template<class ClientType = Botcraft::ManagersClient>
|
||||
std::unique_ptr<ClientType> SetupTestBot(const Botcraft::Vector3<double>& offset = { 0,0,0 }, const Botcraft::GameType gamemode = Botcraft::GameType::Survival)
|
||||
{
|
||||
std::string botname;
|
||||
std::unique_ptr<ClientType> bot = TestManager::GetInstance().GetBot<ClientType>(botname, gamemode);
|
||||
|
||||
const Botcraft::Vector3<double> pos = offset + TestManager::GetInstance().GetCurrentOffset();
|
||||
TestManager::GetInstance().Teleport(botname, pos);
|
||||
|
||||
// Wait for bot to register teleportation
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
if (!Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return local_player->GetPosition().SqrDist(pos) < 1.0;
|
||||
}, 5000))
|
||||
{
|
||||
throw std::runtime_error("Timeout waiting " + botname + " to register teleportation");
|
||||
}
|
||||
|
||||
// Wait for bot to load center and corner view_distance blocks
|
||||
std::shared_ptr<Botcraft::World> world = bot->GetWorld();
|
||||
const int chunk_x = static_cast<int>(std::floor(pos.x / static_cast<double>(Botcraft::CHUNK_WIDTH)));
|
||||
const int chunk_z = static_cast<int>(std::floor(pos.z / static_cast<double>(Botcraft::CHUNK_WIDTH)));
|
||||
// -1 because sometimes corner chunks are not sent, depending on where you are on the current chunk
|
||||
const int view_distance = MinecraftServer::options.view_distance - 1;
|
||||
std::vector<std::pair<int, int>> wait_loaded = {
|
||||
{chunk_x * Botcraft::CHUNK_WIDTH, chunk_z * Botcraft::CHUNK_WIDTH},
|
||||
{(chunk_x + view_distance) * Botcraft::CHUNK_WIDTH - 1, (chunk_z + view_distance) * Botcraft::CHUNK_WIDTH - 1},
|
||||
{(chunk_x - view_distance) * Botcraft::CHUNK_WIDTH, (chunk_z + view_distance) * Botcraft::CHUNK_WIDTH - 1},
|
||||
{(chunk_x + view_distance) * Botcraft::CHUNK_WIDTH - 1, (chunk_z - view_distance) * Botcraft::CHUNK_WIDTH},
|
||||
{(chunk_x - view_distance) * Botcraft::CHUNK_WIDTH, (chunk_z - view_distance) * Botcraft::CHUNK_WIDTH}
|
||||
};
|
||||
|
||||
if (!Botcraft::Utilities::WaitForCondition([&]() {
|
||||
for (size_t i = 0; i < wait_loaded.size(); ++i)
|
||||
{
|
||||
if (!bot->GetWorld()->IsLoaded(Botcraft::Position(wait_loaded[i].first, 2, wait_loaded[i].second)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, 15000))
|
||||
{
|
||||
throw std::runtime_error("Timeout waiting " + botname + " to load surroundings");
|
||||
}
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
#include <botcraft/Game/AssetsManager.hpp>
|
||||
#include <botcraft/Game/Inventory/InventoryManager.hpp>
|
||||
#include <botcraft/Game/Inventory/Window.hpp>
|
||||
|
||||
template<class ClientType = Botcraft::SimpleBehaviourClient>
|
||||
bool GiveItem(std::unique_ptr<ClientType>& bot, const std::string& item_name, const std::string& item_pretty_name, const int quantity = 1)
|
||||
{
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
const Botcraft::Item* item = Botcraft::AssetsManager::getInstance().GetItem(Botcraft::AssetsManager::getInstance().GetItemID(item_name));
|
||||
short receiving_slot = -1;
|
||||
const std::map<short, ProtocolCraft::Slot> slots = inventory_manager->GetPlayerInventory()->GetSlots();
|
||||
for (short i = Botcraft::Window::INVENTORY_HOTBAR_START; i < Botcraft::Window::INVENTORY_OFFHAND_INDEX; ++i)
|
||||
{
|
||||
if (slots.at(i).IsEmptySlot() ||
|
||||
(item->GetId() == slots.at(i).GetItemID() && item->GetStackSize() >= slots.at(i).GetItemCount() + quantity))
|
||||
{
|
||||
receiving_slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// No slot available in the hotbar, check the main inventory
|
||||
if (receiving_slot == -1)
|
||||
{
|
||||
for (short i = Botcraft::Window::INVENTORY_STORAGE_START; i < Botcraft::Window::INVENTORY_HOTBAR_START; ++i)
|
||||
{
|
||||
if (slots.at(i).IsEmptySlot())
|
||||
{
|
||||
receiving_slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (receiving_slot == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& botname = bot->GetNetworkManager()->GetMyName();
|
||||
MinecraftServer::GetInstance().SendLine("give " + botname + " " + item_name + " " + std::to_string(quantity));
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: (?:Given|Gave " + std::to_string(quantity) + ") \\[" + item_pretty_name + "\\](?: \\* " + std::to_string(quantity) + ")? to " + botname + ".*", 5000);
|
||||
return Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return !bot->GetInventoryManager()->GetPlayerInventory()->GetSlot(receiving_slot).IsEmptySlot();
|
||||
}, 5000);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,352 @@
|
||||
#include "MinecraftServer.hpp"
|
||||
|
||||
#include <subprocess/subprocess.h>
|
||||
|
||||
#include <botcraft/Utilities/Logger.hpp>
|
||||
#include <botcraft/Utilities/SleepUtilities.hpp>
|
||||
#include <botcraft/Version.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <array>
|
||||
#include <regex>
|
||||
#include <fstream>
|
||||
|
||||
const std::filesystem::path server_relative_files_path = "../../test_server_files";
|
||||
|
||||
MinecraftServerOptions MinecraftServer::options;
|
||||
|
||||
MinecraftServer::MinecraftServer()
|
||||
{
|
||||
running = false;
|
||||
const std::filesystem::path current_path = std::filesystem::current_path();
|
||||
const std::filesystem::path exe_path = std::filesystem::path(options.argv0).is_absolute() ? std::filesystem::path(options.argv0) : (current_path / options.argv0);
|
||||
server_path = exe_path.parent_path() / "test_servers" / game_version;
|
||||
|
||||
if (std::filesystem::exists(server_path))
|
||||
{
|
||||
std::filesystem::remove_all(server_path);
|
||||
}
|
||||
std::filesystem::create_directories(server_path);
|
||||
|
||||
server_path = std::filesystem::canonical(server_path);
|
||||
InitServerFolder(server_path);
|
||||
|
||||
// Change current path to launch server in the dedicated folder
|
||||
std::filesystem::current_path(server_path);
|
||||
const std::string jar_path = (server_relative_files_path / "server_jars" / ("server_" + game_version + ".jar")).string();
|
||||
const char* command_line[] = { "java", "-Xmx1024M", "-Xms1024M", "-jar", jar_path.c_str(), "nogui", NULL};
|
||||
|
||||
subprocess = std::make_unique<subprocess_s>();
|
||||
int result = subprocess_create(command_line,
|
||||
subprocess_option_combined_stdout_stderr |
|
||||
subprocess_option_inherit_environment |
|
||||
subprocess_option_enable_async |
|
||||
subprocess_option_no_window |
|
||||
subprocess_option_search_user_path,
|
||||
subprocess.get()
|
||||
);
|
||||
// Restore current path to previous value
|
||||
std::filesystem::current_path(current_path);
|
||||
if (result != 0)
|
||||
{
|
||||
LOG_FATAL("Error starting wrapped server process");
|
||||
throw std::runtime_error("Error starting wrapped server process");
|
||||
}
|
||||
running = true;
|
||||
read_thread = std::thread(&MinecraftServer::InternalThreadRead, this);
|
||||
}
|
||||
|
||||
MinecraftServer::~MinecraftServer()
|
||||
{
|
||||
// Ask the server to stop properly
|
||||
SendLine("stop");
|
||||
|
||||
// Wait for the process to finish
|
||||
if (subprocess_join(subprocess.get(), NULL) != 0)
|
||||
{
|
||||
LOG_FATAL("Error joining wrapped server process, killing it");
|
||||
Kill();
|
||||
}
|
||||
running = false;
|
||||
if (subprocess_destroy(subprocess.get()) != 0)
|
||||
{
|
||||
LOG_FATAL("Error destroying wrapped server process");
|
||||
}
|
||||
subprocess.reset();
|
||||
if (read_thread.joinable())
|
||||
{
|
||||
read_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
MinecraftServer& MinecraftServer::GetInstance()
|
||||
{
|
||||
static MinecraftServer instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void MinecraftServer::Initialize()
|
||||
{
|
||||
if (!running)
|
||||
{
|
||||
LOG_WARNING("Trying to initialize a server that is not running");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the server is running and ready
|
||||
// (5 minutes should be enough for the server to start even on a potato)
|
||||
WaitLine(".*: Done \\([\\d.,]+s\\)! For help, type \"help\".*", 300000);
|
||||
|
||||
// Set gamerules
|
||||
InitServerGamerules();
|
||||
|
||||
// Set time to day
|
||||
SendLine("time set day");
|
||||
WaitLine(".*: Set the time to \\d+.*", 5000);
|
||||
}
|
||||
|
||||
std::string MinecraftServer::WaitLine(const long long int timeout_ms)
|
||||
{
|
||||
return WaitLine("", timeout_ms)[0];
|
||||
}
|
||||
|
||||
std::vector<std::string> MinecraftServer::WaitLine(const std::string& regex, const long long int timeout_ms)
|
||||
{
|
||||
if (!running)
|
||||
{
|
||||
return { "" };
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point time_check = std::chrono::steady_clock::now();
|
||||
const std::chrono::steady_clock::time_point start = time_check;
|
||||
std::vector<std::string> output;
|
||||
if (Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(internal_read_mutex);
|
||||
if (lines.size() > 0)
|
||||
{
|
||||
const std::string line = lines.front();
|
||||
lines.pop();
|
||||
if (regex.empty())
|
||||
{
|
||||
LOG_DEBUG("Line caught (no regex):\n" << line);
|
||||
output = { line };
|
||||
return true;
|
||||
}
|
||||
std::smatch match;
|
||||
if (std::regex_match(line, match, std::regex(regex)))
|
||||
{
|
||||
output.resize(match.size());
|
||||
for (size_t i = 0; i < match.size(); ++i)
|
||||
{
|
||||
output[i] = match[i].str();
|
||||
}
|
||||
LOG_DEBUG("Line caught using regex [" << regex << "]:\n" << line);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (timeout_ms == 0)
|
||||
{
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(now - time_check).count() > 30)
|
||||
{
|
||||
time_check = now;
|
||||
LOG_WARNING("Waiting for a server line matching [" << regex << "] since " << std::chrono::duration_cast<std::chrono::seconds>(now - start).count() << " seconds. Infinite loop?");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, timeout_ms))
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
if (regex.empty())
|
||||
{
|
||||
LOG_FATAL("Timeout waiting for server to output a line");
|
||||
throw std::runtime_error("Timeout waiting for server to output a line");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_FATAL("Timeout waiting for server to output a line matching [" << regex << "]");
|
||||
throw std::runtime_error("Timeout waiting for server to output a line matching [" + regex + "]");
|
||||
}
|
||||
}
|
||||
|
||||
void MinecraftServer::SendLine(const std::string& input)
|
||||
{
|
||||
if (!running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FILE* p_stdin = subprocess_stdin(subprocess.get());
|
||||
LOG_DEBUG("Sending to server:\n" << input);
|
||||
std::fputs(input.back() == '\n' ? input.c_str() : (input + '\n').c_str(), p_stdin);
|
||||
std::fflush(p_stdin);
|
||||
}
|
||||
|
||||
std::filesystem::path MinecraftServer::GetStructurePath() const
|
||||
{
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
return server_path / "world" / "generated" / "minecraft" / "structures";
|
||||
#else
|
||||
return server_path / "world" / "structures";
|
||||
#endif
|
||||
}
|
||||
|
||||
void MinecraftServer::Kill()
|
||||
{
|
||||
int result = subprocess_terminate(subprocess.get());
|
||||
if (result != 0)
|
||||
{
|
||||
LOG_FATAL("Error killing wrapped server process");
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
|
||||
void MinecraftServer::InternalThreadRead()
|
||||
{
|
||||
Botcraft::Logger::GetInstance().RegisterThread("read");
|
||||
std::array<char, 256> buffer;
|
||||
std::string line = "";
|
||||
unsigned int bytes_read = 0;
|
||||
while ((bytes_read = subprocess_read_stdout(subprocess.get(), buffer.data(), sizeof(buffer))))
|
||||
{
|
||||
line += std::string(buffer.data(), bytes_read);
|
||||
// Remove windows \r stuff if any
|
||||
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
|
||||
size_t newline_pos = 0;
|
||||
while ((newline_pos = line.find('\n')) != std::string::npos)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(internal_read_mutex);
|
||||
lines.push(line.substr(0, newline_pos));
|
||||
LOG_DEBUG("[SERVER] - " << lines.back());
|
||||
line = line.substr(newline_pos + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MinecraftServer::InitServerFolder(const std::filesystem::path& path)
|
||||
{
|
||||
// Bash script to relaunch the server manually
|
||||
#if _WIN32
|
||||
if (std::ofstream bash = std::ofstream(path / "start.cmd"))
|
||||
#else
|
||||
if (std::ofstream bash = std::ofstream(path / "start.sh"))
|
||||
#endif
|
||||
{
|
||||
bash << "java -Xmx1024M -Xms1024M -jar " << server_relative_files_path.string() << "/server_jars/server_" << game_version << ".jar nogui\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_FATAL("Unable to create start bash script");
|
||||
throw std::runtime_error("Unable to create start bash script");
|
||||
}
|
||||
|
||||
// Accept the EULA
|
||||
if (std::ofstream eula = std::ofstream(path / "eula.txt"))
|
||||
{
|
||||
eula << "eula=true\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_FATAL("Unable to create eula.txt");
|
||||
throw std::runtime_error("Unable to create eula.txt");
|
||||
}
|
||||
|
||||
// Setting the value of all non default properties
|
||||
if (std::ofstream server_props = std::ofstream(path / "server.properties"))
|
||||
{
|
||||
#if PROTOCOL_VERSION < 477 /* < 1.14 */
|
||||
server_props << "difficulty=3" << "\n";
|
||||
#else
|
||||
server_props << "difficulty=hard" << "\n";
|
||||
#endif
|
||||
server_props << "enable-command-block=true" << "\n";
|
||||
#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
|
||||
server_props << "enforce-secure-profile=false" << "\n";
|
||||
#endif
|
||||
// Everyone joins as creative and bots will be manually set to survival upon join
|
||||
server_props << "force-gamemode=true" << "\n";
|
||||
#if PROTOCOL_VERSION < 477 /* < 1.14 */
|
||||
server_props << "gamemode=1" << "\n";
|
||||
#else
|
||||
server_props << "gamemode=creative" << "\n";
|
||||
#endif
|
||||
server_props << "generate-structures=false" << "\n";
|
||||
// generator-settings should be there but it doesn't work on all
|
||||
// supported versions see https://bugs.mojang.com/browse/MC-195468
|
||||
// Set using custom level.dat file instead
|
||||
server_props << "level-seed=botcraft" << "\n";
|
||||
server_props << "level-type=flat" << "\n";
|
||||
server_props << "motd=Botcraft test server" << "\n";
|
||||
#if USE_COMPRESSION
|
||||
server_props << "network-compression-threshold=256" << "\n";
|
||||
#else
|
||||
// This should technically never happen as we need
|
||||
// compression support for protocolCraft to read
|
||||
// structures NBT files
|
||||
server_props << "network-compression-threshold=-1" << "\n";
|
||||
#endif
|
||||
server_props << "online-mode=false" << "\n";
|
||||
server_props << "spawn-monsters=false" << "\n";
|
||||
server_props << "spawn-protection=0" << "\n";
|
||||
server_props << "view-distance=" << options.view_distance << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_FATAL("Unable to create server.properties");
|
||||
throw std::runtime_error("Unable to create server.properties");
|
||||
}
|
||||
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
std::filesystem::create_directories(path / "world" / "generated" / "minecraft");
|
||||
#else
|
||||
std::filesystem::create_directories(path / "world");
|
||||
#endif
|
||||
|
||||
// Setup level.dat
|
||||
std::filesystem::copy_file(path / server_relative_files_path / "runtime" / "level.dat", path / "world" / "level.dat");
|
||||
|
||||
// Setup structures files
|
||||
std::filesystem::copy(
|
||||
path / server_relative_files_path / "runtime" / "structures",
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
path / "world" / "generated" / "minecraft" / "structures",
|
||||
#else
|
||||
path / "world" / "structures",
|
||||
#endif
|
||||
std::filesystem::copy_options::recursive);
|
||||
}
|
||||
|
||||
void MinecraftServer::SetGamerule(const std::string& gamerule, const std::string& value)
|
||||
{
|
||||
SendLine("gamerule " + gamerule + " " + value);
|
||||
WaitLine(".*: Game ?rule " + gamerule + " (?:is now set to:|has been updated to) " + value + ".*", 5000);
|
||||
}
|
||||
|
||||
void MinecraftServer::InitServerGamerules()
|
||||
{
|
||||
SetGamerule("announceAdvancements", "false");
|
||||
#if PROTOCOL_VERSION > 485 /* > 1.14.2 */
|
||||
SetGamerule("disableRaids", "true");
|
||||
#endif
|
||||
SetGamerule("doDaylightCycle", "false");
|
||||
SetGamerule("doFireTick", "false");
|
||||
#if PROTOCOL_VERSION > 498 /* > 1.14.4 */
|
||||
SetGamerule("doInsomnia", "false");
|
||||
#endif
|
||||
SetGamerule("doMobSpawning", "false");
|
||||
#if PROTOCOL_VERSION > 575 /* > 1.15.1 */
|
||||
SetGamerule("doPatrolSpawning", "false");
|
||||
SetGamerule("doTraderSpawning", "false");
|
||||
#endif
|
||||
SetGamerule("doWeatherCycle", "false");
|
||||
#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
|
||||
SetGamerule("doWardenSpawning", "false");
|
||||
#endif
|
||||
SetGamerule("mobGriefing", "false");
|
||||
SetGamerule("randomTickSpeed", "0");
|
||||
SetGamerule("spawnRadius", "0");
|
||||
SetGamerule("spectatorsGenerateChunks", "true");
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
#include "TestManager.hpp"
|
||||
#include "MinecraftServer.hpp"
|
||||
|
||||
#include <catch2/catch_test_case_info.hpp>
|
||||
|
||||
#include <protocolCraft/Types/NBT/NBT.hpp>
|
||||
|
||||
#include <botcraft/Network/NetworkManager.hpp>
|
||||
#include <botcraft/Game/Entities/EntityManager.hpp>
|
||||
#include <botcraft/Game/Entities/LocalPlayer.hpp>
|
||||
#include <botcraft/Utilities/Logger.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
|
||||
std::string ReplaceCharacters(const std::string& in, const std::vector<std::pair<char, std::string>>& replacements = { {'"', "\\\""}, {'\n', "\\n"} });
|
||||
|
||||
TestManager::TestManager()
|
||||
{
|
||||
current_offset = {spacing_x, 2, 2 * spacing_z };
|
||||
current_test_index = 0;
|
||||
bot_index = 0;
|
||||
}
|
||||
|
||||
TestManager::~TestManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TestManager& TestManager::GetInstance()
|
||||
{
|
||||
static TestManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
const Botcraft::Position& TestManager::GetCurrentOffset() const
|
||||
{
|
||||
return current_offset;
|
||||
}
|
||||
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
void TestManager::SetBlock(const std::string& name, const Botcraft::Position& pos, const std::map<std::string, std::string>& blockstates, const std::map<std::string, std::string>& metadata, const bool escape_metadata) const
|
||||
#else
|
||||
void TestManager::SetBlock(const std::string& name, const Botcraft::Position& pos, const int block_variant, const std::map<std::string, std::string>& metadata, const bool escape_metadata) const
|
||||
#endif
|
||||
{
|
||||
MakeSureLoaded(pos);
|
||||
std::stringstream command;
|
||||
command
|
||||
<< "setblock" << " "
|
||||
<< pos.x << " "
|
||||
<< pos.y << " "
|
||||
<< pos.z << " "
|
||||
<< ((name.size() > 10 && name.substr(0, 10) == "minecraft:") ? "" : "minecraft:") << name;
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
if (!blockstates.empty())
|
||||
{
|
||||
command << "[";
|
||||
int index = 0;
|
||||
for (const auto& [k, v] : blockstates)
|
||||
{
|
||||
command << k << "=";
|
||||
if (v.find(' ') != std::string::npos ||
|
||||
v.find(',') != std::string::npos ||
|
||||
v.find(':') != std::string::npos)
|
||||
{
|
||||
// Make sure the whole string is between quotes and all internal quotes are escaped
|
||||
command << "\"" << ReplaceCharacters(v) << "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
command << v;
|
||||
}
|
||||
command << (index == blockstates.size() - 1 ? "" : ",");
|
||||
index += 1;
|
||||
}
|
||||
command << "]";
|
||||
}
|
||||
#else
|
||||
command << " "
|
||||
<< block_variant << " "
|
||||
<< "replace" << " ";
|
||||
#endif
|
||||
if (!metadata.empty())
|
||||
{
|
||||
command << "{";
|
||||
int index = 0;
|
||||
for (const auto& [k, v] : metadata)
|
||||
{
|
||||
command << k << ":";
|
||||
if (escape_metadata &&
|
||||
(v.find(' ') != std::string::npos ||
|
||||
v.find(',') != std::string::npos ||
|
||||
v.find(':') != std::string::npos)
|
||||
)
|
||||
{
|
||||
// Make sure the whole string is between quotes and all internal quotes are escaped
|
||||
command << "\"" << ReplaceCharacters(v) << "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
command << v;
|
||||
}
|
||||
command << (index == metadata.size() - 1 ? "" : ",");
|
||||
index += 1;
|
||||
}
|
||||
command << "}";
|
||||
}
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
command << " replace";
|
||||
#endif
|
||||
MinecraftServer::GetInstance().SendLine(command.str());
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
MinecraftServer::GetInstance().WaitLine(
|
||||
".*: Changed the block at "
|
||||
+ std::to_string(pos.x) + ", "
|
||||
+ std::to_string(pos.y) + ", "
|
||||
+ std::to_string(pos.z) + ".*", 5000);
|
||||
#else
|
||||
MinecraftServer::GetInstance().WaitLine(".*: Block placed.*", 5000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestManager::CreateBook(const Botcraft::Position& pos, const std::vector<std::string>& pages, const std::string& facing, const std::string& title, const std::string& author, const std::vector<std::string>& description)
|
||||
{
|
||||
int facing_id = 0;
|
||||
if (facing == "south")
|
||||
{
|
||||
facing_id = 0;
|
||||
}
|
||||
else if (facing == "west")
|
||||
{
|
||||
facing_id = 1;
|
||||
}
|
||||
else if (facing == "north")
|
||||
{
|
||||
facing_id = 2;
|
||||
}
|
||||
else if (facing == "east")
|
||||
{
|
||||
facing_id = 3;
|
||||
}
|
||||
|
||||
std::stringstream command;
|
||||
|
||||
command
|
||||
#if PROTOCOL_VERSION < 477 /* < 1.14 */
|
||||
<< "summon" << " "
|
||||
<< "minecraft:item_frame" << " "
|
||||
#else
|
||||
<< "setblock" << " "
|
||||
#endif
|
||||
<< pos.x << " "
|
||||
<< pos.y << " "
|
||||
<< pos.z << " "
|
||||
#if PROTOCOL_VERSION < 477 /* < 1.14 */
|
||||
<< "{Facing:" << facing_id << ","
|
||||
<< "Item:{"
|
||||
#else
|
||||
<< "minecraft:lectern"
|
||||
<< "[facing=" << facing << ",has_book=true]"
|
||||
<< "{"
|
||||
<< "Book:{"
|
||||
#endif
|
||||
<< "id:\"written_book\"" << ","
|
||||
<< "Count:1" << ","
|
||||
<< "tag:{"
|
||||
<< "pages:[";
|
||||
for (size_t i = 0; i < pages.size(); ++i)
|
||||
{
|
||||
command
|
||||
<< "\"{"
|
||||
<< "\\\"text\\\"" << ":" << "\\\"" << ReplaceCharacters(pages[i], { {'\n', "\\\\n"}, {'"', "\\\\\\\""} }) << "\\\""
|
||||
<< "}\"" << ((i < pages.size() - 1) ? "," : "");
|
||||
}
|
||||
command << "]";
|
||||
if (!title.empty())
|
||||
{
|
||||
command << ","
|
||||
<< "title" << ":" << "\"" << ReplaceCharacters(title) << "\"";
|
||||
}
|
||||
if (!author.empty())
|
||||
{
|
||||
command << ","
|
||||
<< "author" << ":" << "\"" << ReplaceCharacters(author) << "\"";
|
||||
}
|
||||
if (!description.empty())
|
||||
{
|
||||
command << "," << "display" << ":"
|
||||
<< "{Lore:[";
|
||||
for (size_t i = 0; i < description.size(); ++i)
|
||||
{
|
||||
command
|
||||
<< "\""
|
||||
#if PROTOCOL_VERSION < 735 /* < 1.16 */
|
||||
// Just a list of strings is working before 1.16(?)
|
||||
<< ReplaceCharacters(description[i])
|
||||
#else
|
||||
// After, a JSON text element is required
|
||||
<< "{" << "\\\"text\\\"" << ":" << "\\\"" << ReplaceCharacters(description[i], { { '\n', "\\\\n"}, { '"', "\\\\\\\"" } }) << "\\\"" << "}"
|
||||
#endif
|
||||
<< "\"" << ((i < description.size() - 1) ? "," : "");
|
||||
}
|
||||
command << "]}";
|
||||
}
|
||||
command
|
||||
<< "}" // tag
|
||||
<< "}" // Item
|
||||
<< "}"; // Main
|
||||
|
||||
MinecraftServer::GetInstance().SendLine(command.str());
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */ && PROTOCOL_VERSION < 477 /* < 1.14 */
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: Summoned new Item Frame.*", 5000);
|
||||
#elif PROTOCOL_VERSION == 340 /* 1.12.2 */
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: Object successfully summoned.*", 5000);
|
||||
#else
|
||||
MinecraftServer::GetInstance().WaitLine(
|
||||
".*: Changed the block at "
|
||||
+ std::to_string(pos.x) + ", "
|
||||
+ std::to_string(pos.y) + ", "
|
||||
+ std::to_string(pos.z) + ".*", 5000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestManager::Teleport(const std::string& name, const Botcraft::Vector3<double>& pos, const float yaw, const float pitch) const
|
||||
{
|
||||
std::stringstream command;
|
||||
command
|
||||
<< "teleport" << " "
|
||||
<< name << " "
|
||||
<< pos.x << " "
|
||||
<< pos.y << " "
|
||||
<< pos.z << " "
|
||||
<< yaw << " "
|
||||
<< pitch;
|
||||
MinecraftServer::GetInstance().SendLine(command.str());
|
||||
MinecraftServer::GetInstance().WaitLine(".*?Teleported " + name + " to.*", 5000);
|
||||
}
|
||||
|
||||
Botcraft::Position TestManager::GetStructureSize(const std::string& filename) const
|
||||
{
|
||||
std::string no_space_filename = ReplaceCharacters(filename, { {' ', "_"} });
|
||||
// If there is a # in the file name, only use what's before (useful to have multiple tests sharing the same structure file)
|
||||
size_t split_index = no_space_filename.find('#');
|
||||
if (split_index != std::string::npos)
|
||||
{
|
||||
no_space_filename = no_space_filename.substr(0, split_index);
|
||||
}
|
||||
const std::filesystem::path filepath = MinecraftServer::GetInstance().GetStructurePath() / (no_space_filename + ".nbt");
|
||||
|
||||
if (!std::filesystem::exists(filepath))
|
||||
{
|
||||
return GetStructureSize("_default");
|
||||
}
|
||||
std::ifstream file(filepath.string(), std::ios::in | std::ios::binary);
|
||||
file.unsetf(std::ios::skipws);
|
||||
|
||||
ProtocolCraft::NBT::Value nbt;
|
||||
file >> nbt;
|
||||
file.close();
|
||||
|
||||
// TODO: deal with recursive structures (structures containing structure blocks) ?
|
||||
return nbt["size"].as_list_of<int>();
|
||||
}
|
||||
|
||||
void TestManager::CreateTPSign(const Botcraft::Position& src, const Botcraft::Vector3<double>& dst, const std::vector<std::string>& texts, const std::string& facing, const TestSucess success) const
|
||||
{
|
||||
Botcraft::Position offset_target;
|
||||
int block_rotation = 0;
|
||||
if (facing == "north")
|
||||
{
|
||||
block_rotation = 0;
|
||||
offset_target = dst + Botcraft::Position(0, 0, -1);
|
||||
}
|
||||
else if (facing == "south")
|
||||
{
|
||||
block_rotation = 8;
|
||||
offset_target = dst + Botcraft::Position(0, 0, 1);
|
||||
}
|
||||
else if (facing == "east")
|
||||
{
|
||||
block_rotation = 12;
|
||||
offset_target = dst + Botcraft::Position(-1, 0, 0);
|
||||
}
|
||||
else if (facing == "west")
|
||||
{
|
||||
block_rotation = 4;
|
||||
offset_target = dst + Botcraft::Position(1, 0, 0);
|
||||
}
|
||||
std::string text_color;
|
||||
switch (success)
|
||||
{
|
||||
case TestSucess::None:
|
||||
text_color = "black";
|
||||
break;
|
||||
case TestSucess::Success:
|
||||
text_color = "dark_green";
|
||||
break;
|
||||
case TestSucess::Failure:
|
||||
text_color = "red";
|
||||
break;
|
||||
case TestSucess::ExpectedFailure:
|
||||
text_color = "gold";
|
||||
break;
|
||||
}
|
||||
#if PROTOCOL_VERSION < 763 /* < 1.20 */
|
||||
std::map<std::string, std::string> lines;
|
||||
#else
|
||||
std::vector<std::string> lines;
|
||||
lines.reserve(4);
|
||||
#endif
|
||||
for (size_t i = 0; i < std::min(texts.size(), static_cast<size_t>(4)); ++i)
|
||||
{
|
||||
std::stringstream line;
|
||||
line
|
||||
<< "{"
|
||||
<< "\"text\":\"" << texts[i] << "\"" << ",";
|
||||
if (i == 0)
|
||||
{
|
||||
line
|
||||
<< "\"underlined\"" << ":" << "false" << ","
|
||||
<< "\"color\"" << ":" << "\"" << "black" << "\"" << ","
|
||||
<< "\"clickEvent\"" << ":" << "{"
|
||||
<< "\"action\"" << ":" << "\"run_command\"" << ","
|
||||
<< "\"value\"" << ":" << "\""
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
<< "teleport @s" << " " << offset_target.x << " " << offset_target.y << " " << offset_target.z
|
||||
<< " " << "facing" << " " << dst.x << " " << dst.y << " " << dst.z
|
||||
#else
|
||||
<< "teleport @s" << " " << dst.x << " " << dst.y << " " << dst.z
|
||||
#endif
|
||||
<< "\""
|
||||
<< "}";
|
||||
}
|
||||
else
|
||||
{
|
||||
line
|
||||
<< "\"underlined\"" << ":" << "true" << ","
|
||||
<< "\"color\"" << ":" << "\"" << text_color << "\"";
|
||||
}
|
||||
line << "}";
|
||||
#if PROTOCOL_VERSION < 763 /* < 1.20 */
|
||||
lines.insert({ "Text" + std::to_string(i + 1), line.str() });
|
||||
#else
|
||||
lines.push_back(line.str());
|
||||
#endif
|
||||
}
|
||||
// There is a bug in version 1.14 (and prereleases) that requires all 4 lines
|
||||
// to be specified or the server can't read the text. See: https://bugs.mojang.com/browse/MC-144316
|
||||
#if PROTOCOL_VERSION > 459 /* > 1.13.2 */ && PROTOCOL_VERSION < 478 /* < 1.14.1 */
|
||||
for (size_t i = texts.size(); i < 4ULL; ++i)
|
||||
{
|
||||
lines.insert({ "Text" + std::to_string(i + 1), "{\"text\":\"\"}" });
|
||||
}
|
||||
#endif
|
||||
#if PROTOCOL_VERSION > 762 /* > 1.19.4 */ // In 1.20 texts are sent as a list with exactly 4 elements
|
||||
std::stringstream text_content;
|
||||
text_content << "{\"messages\":[";
|
||||
for (size_t i = 0; i < 4ULL; ++i)
|
||||
{
|
||||
if (i < lines.size())
|
||||
{
|
||||
text_content << "\"" << ReplaceCharacters(lines[i]) << "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
text_content << "\"" << ReplaceCharacters("{\"text\":\"\"}") << "\"";
|
||||
}
|
||||
if (i != 3ULL)
|
||||
{
|
||||
text_content << ",";
|
||||
}
|
||||
}
|
||||
text_content << "]}";
|
||||
const std::string text_content_str = text_content.str();
|
||||
#endif
|
||||
|
||||
SetBlock(
|
||||
#if PROTOCOL_VERSION < 393 /* < 1.13 */
|
||||
"standing_sign",
|
||||
#elif PROTOCOL_VERSION < 477 /* < 1.14 */
|
||||
"sign",
|
||||
#else
|
||||
"oak_sign",
|
||||
#endif
|
||||
src,
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
{
|
||||
{ "rotation", std::to_string(block_rotation) }
|
||||
},
|
||||
#else
|
||||
block_rotation,
|
||||
#endif
|
||||
#if PROTOCOL_VERSION < 763 /* < 1.20 */
|
||||
lines
|
||||
#else
|
||||
{
|
||||
{ "is_waxed", "true" },
|
||||
{ "front_text", text_content_str },
|
||||
{ "back_text", text_content_str },
|
||||
},
|
||||
false
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
void TestManager::LoadStructure(const std::string& filename, const Botcraft::Position& pos, const Botcraft::Position& load_offset) const
|
||||
{
|
||||
std::string no_space_filename = ReplaceCharacters(filename, { {' ', "_"} });
|
||||
// If there is a # in the file name, only use what's before (useful to have multiple tests sharing the same structure file)
|
||||
size_t split_index = no_space_filename.find('#');
|
||||
if (split_index != std::string::npos)
|
||||
{
|
||||
no_space_filename = no_space_filename.substr(0, split_index);
|
||||
}
|
||||
const std::string& loaded = std::filesystem::exists(MinecraftServer::GetInstance().GetStructurePath() / (no_space_filename + ".nbt")) ?
|
||||
no_space_filename :
|
||||
"_default";
|
||||
|
||||
SetBlock(
|
||||
"structure_block",
|
||||
pos,
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
{},
|
||||
#else
|
||||
0,
|
||||
#endif
|
||||
{
|
||||
{"mode", "LOAD"},
|
||||
{"name", loaded},
|
||||
{"posX", std::to_string(load_offset.x)},
|
||||
{"posY", std::to_string(load_offset.y)},
|
||||
{"posZ", std::to_string(load_offset.z)},
|
||||
{"showboundingbox", "1"}
|
||||
}
|
||||
);
|
||||
SetBlock("redstone_block", pos + Botcraft::Position(0, 1, 0));
|
||||
}
|
||||
|
||||
void TestManager::MakeSureLoaded(const Botcraft::Position& pos) const
|
||||
{
|
||||
std::shared_ptr<Botcraft::World> world = chunk_loader->GetWorld();
|
||||
if (chunk_loader->GetWorld()->IsLoaded(pos))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Teleport(chunk_loader_name, pos);
|
||||
|
||||
// Wait for bot to load center and corner view_distance blocks
|
||||
const int chunk_x = static_cast<int>(std::floor(pos.x / static_cast<double>(Botcraft::CHUNK_WIDTH)));
|
||||
const int chunk_z = static_cast<int>(std::floor(pos.z / static_cast<double>(Botcraft::CHUNK_WIDTH)));
|
||||
// -1 because sometimes corner chunks are not sent
|
||||
const int view_distance = MinecraftServer::options.view_distance - 1;
|
||||
std::vector<std::pair<int, int>> wait_loaded = {
|
||||
{chunk_x * Botcraft::CHUNK_WIDTH, chunk_z * Botcraft::CHUNK_WIDTH},
|
||||
{(chunk_x + view_distance) * Botcraft::CHUNK_WIDTH - 1, (chunk_z + view_distance) * Botcraft::CHUNK_WIDTH - 1},
|
||||
{(chunk_x - view_distance) * Botcraft::CHUNK_WIDTH, (chunk_z + view_distance) * Botcraft::CHUNK_WIDTH - 1},
|
||||
{(chunk_x + view_distance) * Botcraft::CHUNK_WIDTH - 1, (chunk_z - view_distance) * Botcraft::CHUNK_WIDTH},
|
||||
{(chunk_x - view_distance) * Botcraft::CHUNK_WIDTH, (chunk_z - view_distance) * Botcraft::CHUNK_WIDTH}
|
||||
};
|
||||
|
||||
if (!Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
for (size_t i = 0; i < wait_loaded.size(); ++i)
|
||||
{
|
||||
if (!world->IsLoaded(Botcraft::Position(wait_loaded[i].first, pos.y, wait_loaded[i].second)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, 15000))
|
||||
{
|
||||
LOG_ERROR("Timeout waiting " << chunk_loader_name << " to load surroundings from " << chunk_loader->GetLocalPlayer()->GetPosition());
|
||||
for (size_t i = 0; i < wait_loaded.size(); ++i)
|
||||
{
|
||||
const Botcraft::Position pos(wait_loaded[i].first, pos.y, wait_loaded[i].second);
|
||||
LOG_ERROR(pos << " is" << (world->IsLoaded(pos) ? "" : " not") << " loaded");
|
||||
}
|
||||
std::stringstream loaded;
|
||||
loaded << "\n";
|
||||
for (const auto& c : *world->GetChunks())
|
||||
{
|
||||
loaded << c.first.first << "\t" << c.first.second << ";";
|
||||
}
|
||||
LOG_ERROR("Loaded chunks: " << loaded.str());
|
||||
throw std::runtime_error("Timeout waiting " + chunk_loader_name + " to load surroundings");
|
||||
}
|
||||
}
|
||||
|
||||
void TestManager::SetGameMode(const std::string& name, const Botcraft::GameType gamemode) const
|
||||
{
|
||||
std::string gamemode_string;
|
||||
switch (gamemode)
|
||||
{
|
||||
case Botcraft::GameType::Survival:
|
||||
gamemode_string = "survival";
|
||||
break;
|
||||
case Botcraft::GameType::Creative:
|
||||
gamemode_string = "creative";
|
||||
break;
|
||||
case Botcraft::GameType::Adventure:
|
||||
gamemode_string = "adventure";
|
||||
break;
|
||||
case Botcraft::GameType::Spectator:
|
||||
gamemode_string = "spectator";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
std::stringstream command;
|
||||
command
|
||||
<< "gamemode" << " "
|
||||
<< gamemode_string << " "
|
||||
<< name;
|
||||
MinecraftServer::GetInstance().SendLine(command.str());
|
||||
MinecraftServer::GetInstance().WaitLine(".*? Set " + name + "'s game mode to.*", 5000);
|
||||
}
|
||||
|
||||
|
||||
void TestManager::testRunStarting(Catch::TestRunInfo const& test_run_info)
|
||||
{
|
||||
// Make sure the server is running and ready before the first test run
|
||||
MinecraftServer::GetInstance().Initialize();
|
||||
// Retrieve header size
|
||||
header_size = GetStructureSize("_header_running");
|
||||
chunk_loader = GetBot(chunk_loader_name, Botcraft::GameType::Spectator);
|
||||
}
|
||||
|
||||
void TestManager::testCaseStarting(Catch::TestCaseInfo const& test_info)
|
||||
{
|
||||
// Retrieve test structure size
|
||||
current_test_size = GetStructureSize(test_info.name);
|
||||
LoadStructure("_header_running", Botcraft::Position(current_offset.x, 0, spacing_z - header_size.z));
|
||||
current_offset.z = 2 * spacing_z;
|
||||
}
|
||||
|
||||
void TestManager::testCasePartialStarting(Catch::TestCaseInfo const& test_info, uint64_t part_number)
|
||||
{
|
||||
// Load header
|
||||
current_header_position = current_offset - Botcraft::Position(0, 2, 0);
|
||||
LoadStructure("_header_running", current_header_position);
|
||||
current_offset.z += header_size.z;
|
||||
// Load test structure
|
||||
LoadStructure(test_info.name, current_offset + Botcraft::Position(0, -1, 0), Botcraft::Position(0, 1, 0));
|
||||
current_test_case_failures.clear();
|
||||
section_stack = { std::filesystem::path(test_info.lineInfo.file).stem().string() };
|
||||
}
|
||||
|
||||
void TestManager::sectionStarting(Catch::SectionInfo const& section_info)
|
||||
{
|
||||
section_stack.push_back(section_info.name);
|
||||
}
|
||||
|
||||
void TestManager::assertionEnded(Catch::AssertionStats const& assertion_stats)
|
||||
{
|
||||
if (!assertion_stats.assertionResult.succeeded())
|
||||
{
|
||||
const std::filesystem::path file_path = std::filesystem::relative(assertion_stats.assertionResult.getSourceInfo().file, BASE_SOURCE_DIR);
|
||||
current_test_case_failures.push_back(
|
||||
ReplaceCharacters(file_path.string(), {{'\\', "/"}}) + ":" +
|
||||
std::to_string(assertion_stats.assertionResult.getSourceInfo().line) + "\n\n" +
|
||||
assertion_stats.assertionResult.getExpressionInMacro() + "\n\n"
|
||||
"with expansion:" + "\n" +
|
||||
assertion_stats.assertionResult.getExpandedExpression()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void TestManager::testCasePartialEnded(Catch::TestCaseStats const& test_case_stats, uint64_t part_number)
|
||||
{
|
||||
const bool passed = test_case_stats.totals.assertions.allPassed();
|
||||
// Replace header with proper test result
|
||||
LoadStructure(passed ? "_header_success" : (test_case_stats.testInfo->okToFail() ? "_header_expected_fail" : "_header_fail"), current_header_position);
|
||||
// Create TP sign for the partial that just ended
|
||||
CreateTPSign(
|
||||
Botcraft::Position(-2 * (current_test_index + 1), 2, -2 * part_number - 5),
|
||||
Botcraft::Vector3(current_offset.x, 2, current_offset.z - 1),
|
||||
section_stack, "north", passed ? TestSucess::Success : (test_case_stats.testInfo->okToFail() ? TestSucess::ExpectedFailure : TestSucess::Failure)
|
||||
);
|
||||
// Create back to spawn sign for the section that just ended
|
||||
CreateTPSign(
|
||||
Botcraft::Position(current_offset.x, 2, current_offset.z - 1),
|
||||
Botcraft::Vector3(-2 * (current_test_index + 1), 2, -2 * static_cast<int>(part_number) - 5),
|
||||
section_stack, "south", passed ? TestSucess::Success : (test_case_stats.testInfo->okToFail() ? TestSucess::ExpectedFailure : TestSucess::Failure)
|
||||
);
|
||||
if (!passed)
|
||||
{
|
||||
CreateBook(
|
||||
Botcraft::Position(current_offset.x + 1, 2, current_offset.z - header_size.z),
|
||||
current_test_case_failures,
|
||||
"north",
|
||||
test_case_stats.testInfo->name + "#" + std::to_string(part_number),
|
||||
"Botcraft Test Framework",
|
||||
section_stack
|
||||
);
|
||||
}
|
||||
current_offset.z += current_test_size.z + spacing_z;
|
||||
|
||||
// Kill all items that could exist on the floor
|
||||
MinecraftServer::GetInstance().SendLine("kill @e[type=item]");
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
// In 1.12.2 server sends one line per entity killed so we can't wait without knowing
|
||||
// how many there were. Just assume the command worked
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: (?:Killed|No entity was found).*", 5000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestManager::testCaseEnded(Catch::TestCaseStats const& test_case_stats)
|
||||
{
|
||||
const bool passed = test_case_stats.totals.assertions.allPassed();
|
||||
LoadStructure(passed ? "_header_success" : (test_case_stats.testInfo->okToFail() ? "_header_expected_fail" : "_header_fail"), Botcraft::Position(current_offset.x, 0, spacing_z - header_size.z));
|
||||
// Create Sign to TP to current test
|
||||
CreateTPSign(
|
||||
Botcraft::Position(-2 * (current_test_index + 1), 2, -2),
|
||||
Botcraft::Vector3(current_offset.x, 2, spacing_z - 1),
|
||||
{ std::filesystem::path(test_case_stats.testInfo->lineInfo.file).stem().string(), test_case_stats.testInfo->name },
|
||||
"north", passed ? TestSucess::Success : (test_case_stats.testInfo->okToFail() ? TestSucess::ExpectedFailure : TestSucess::Failure)
|
||||
);
|
||||
// Create sign to TP to TP back to spawn
|
||||
CreateTPSign(
|
||||
Botcraft::Position(current_offset.x, 2, spacing_z - 1),
|
||||
Botcraft::Vector3(-2 * (current_test_index + 1), 2, -2),
|
||||
{ std::filesystem::path(test_case_stats.testInfo->lineInfo.file).stem().string(), test_case_stats.testInfo->name },
|
||||
"south", passed ? TestSucess::Success : (test_case_stats.testInfo->okToFail() ? TestSucess::ExpectedFailure : TestSucess::Failure)
|
||||
);
|
||||
current_test_index += 1;
|
||||
current_offset.x += std::max(current_test_size.x, header_size.x) + spacing_x;
|
||||
}
|
||||
|
||||
void TestManager::testRunEnded(Catch::TestRunStats const& test_run_info)
|
||||
{
|
||||
if (chunk_loader)
|
||||
{
|
||||
chunk_loader->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TestManagerListener::testRunStarting(Catch::TestRunInfo const& test_run_info)
|
||||
{
|
||||
TestManager::GetInstance().testRunStarting(test_run_info);
|
||||
}
|
||||
|
||||
void TestManagerListener::testCaseStarting(Catch::TestCaseInfo const& test_info)
|
||||
{
|
||||
TestManager::GetInstance().testCaseStarting(test_info);
|
||||
}
|
||||
|
||||
void TestManagerListener::testCasePartialStarting(Catch::TestCaseInfo const& test_info, uint64_t part_number)
|
||||
{
|
||||
TestManager::GetInstance().testCasePartialStarting(test_info, part_number);
|
||||
}
|
||||
|
||||
void TestManagerListener::sectionStarting(Catch::SectionInfo const& section_info)
|
||||
{
|
||||
TestManager::GetInstance().sectionStarting(section_info);
|
||||
}
|
||||
|
||||
void TestManagerListener::assertionEnded(Catch::AssertionStats const& assertion_stats)
|
||||
{
|
||||
TestManager::GetInstance().assertionEnded(assertion_stats);
|
||||
}
|
||||
|
||||
void TestManagerListener::testCasePartialEnded(Catch::TestCaseStats const& test_case_stats, uint64_t part_number)
|
||||
{
|
||||
TestManager::GetInstance().testCasePartialEnded(test_case_stats, part_number);
|
||||
}
|
||||
|
||||
void TestManagerListener::testCaseEnded(Catch::TestCaseStats const& test_case_stats)
|
||||
{
|
||||
TestManager::GetInstance().testCaseEnded(test_case_stats);
|
||||
}
|
||||
|
||||
void TestManagerListener::testRunEnded(Catch::TestRunStats const& test_run_info)
|
||||
{
|
||||
TestManager::GetInstance().testRunEnded(test_run_info);
|
||||
}
|
||||
CATCH_REGISTER_LISTENER(TestManagerListener)
|
||||
|
||||
std::string ReplaceCharacters(const std::string& in, const std::vector<std::pair<char, std::string>>& replacements)
|
||||
{
|
||||
std::string output;
|
||||
output.reserve(in.size());
|
||||
|
||||
for (size_t i = 0; i < in.size(); ++i)
|
||||
{
|
||||
bool found = false;
|
||||
for (size_t j = 0; j < replacements.size(); ++j)
|
||||
{
|
||||
if (replacements[j].first == in[i])
|
||||
{
|
||||
output += replacements[j].second;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
output += in[i];
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <catch2/catch_session.hpp>
|
||||
|
||||
#include <botcraft/Utilities/Logger.hpp>
|
||||
|
||||
#include "MinecraftServer.hpp"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
MinecraftServer::options.argv0 = argv[0];
|
||||
Botcraft::Logger::GetInstance().RegisterThread("main");
|
||||
|
||||
Catch::Session session;
|
||||
|
||||
// Parse the command line
|
||||
int ret = session.applyCommandLine(argc, argv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Adapt botcraft logger to catch loglevel
|
||||
switch (session.config().verbosity())
|
||||
{
|
||||
case Catch::Verbosity::Quiet:
|
||||
Botcraft::Logger::GetInstance().SetLogLevel(Botcraft::LogLevel::Fatal);
|
||||
break;
|
||||
case Catch::Verbosity::Normal:
|
||||
Botcraft::Logger::GetInstance().SetLogLevel(Botcraft::LogLevel::Warning);
|
||||
break;
|
||||
case Catch::Verbosity::High:
|
||||
Botcraft::Logger::GetInstance().SetLogLevel(Botcraft::LogLevel::Debug);
|
||||
break;
|
||||
}
|
||||
|
||||
return session.run();
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "TestManager.hpp"
|
||||
#include "MinecraftServer.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
#include <botcraft/AI/SimpleBehaviourClient.hpp>
|
||||
#include <botcraft/AI/Tasks/BaseTasks.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
TEST_CASE("say")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
|
||||
bot->SyncAction(5000, Botcraft::Say, "Hello, world!");
|
||||
const std::string& botname = bot->GetNetworkManager()->GetMyName();
|
||||
|
||||
REQUIRE_NOTHROW(MinecraftServer::GetInstance().WaitLine(".*: (?:\\[Not Secure\\] )?[[<]" + botname + "[>\\]] Hello, world!.*", 5000));
|
||||
}
|
||||
|
||||
bool IsLitRedstoneLamp(const Botcraft::Blockstate* block)
|
||||
{
|
||||
if (block == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
return block->GetName() == "minecraft:redstone_lamp" && block->GetVariableValue("lit") == "true";
|
||||
#else
|
||||
return block->GetName() == "minecraft:lit_redstone_lamp";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("interact")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
|
||||
const Botcraft::Position lever = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(1, 1, 1);
|
||||
const Botcraft::Position lamp = lever - Botcraft::Position(0, 1, 0);
|
||||
|
||||
// Wait for at least one tick because otherwise for some versions the server sometimes
|
||||
// does not register the new position (? not sure about what happens here)
|
||||
Botcraft::Utilities::WaitForCondition([]() -> bool
|
||||
{
|
||||
return false;
|
||||
}, 100);
|
||||
|
||||
bot->SyncAction(5000, Botcraft::InteractWithBlock, lever, Botcraft::PlayerDiggingFace::Up, true);
|
||||
|
||||
std::shared_ptr<Botcraft::World> world = bot->GetWorld();
|
||||
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
const Botcraft::Blockstate* block = world->GetBlock(lamp);
|
||||
return IsLitRedstoneLamp(block);
|
||||
}, 5000));
|
||||
}
|
||||
|
||||
TEST_CASE("get day time")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
|
||||
auto day_time = GENERATE(1000, 5000, 10000, 15000, 20000, 23500);
|
||||
|
||||
// Sent time set command
|
||||
MinecraftServer::GetInstance().SendLine("time set " + std::to_string(day_time));
|
||||
MinecraftServer::GetInstance().WaitLine(".*: Set the time to " + std::to_string(day_time) + ".*", 5000);
|
||||
|
||||
Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return bot->GetDayTime() == day_time;
|
||||
}, 5000);
|
||||
CHECK(bot->GetDayTime() == day_time);
|
||||
|
||||
// Reset the time to day
|
||||
MinecraftServer::GetInstance().SendLine("time set day");
|
||||
MinecraftServer::GetInstance().WaitLine(".*: Set the time to 1000.*", 5000);
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
#include "TestManager.hpp"
|
||||
#include "MinecraftServer.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include <botcraft/AI/SimpleBehaviourClient.hpp>
|
||||
#include <botcraft/AI/Tasks/DigTask.hpp>
|
||||
#include <botcraft/Game/Inventory/InventoryManager.hpp>
|
||||
|
||||
void TestDig(std::unique_ptr<Botcraft::SimpleBehaviourClient>& bot, const Botcraft::Position& pos, const double time_s)
|
||||
{
|
||||
// Wait to be on ground
|
||||
Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return bot->GetLocalPlayer()->GetOnGround();
|
||||
}, 5000);
|
||||
|
||||
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
||||
bot->SyncAction(30000, Botcraft::Dig, pos, true, Botcraft::PlayerDiggingFace::North);
|
||||
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
|
||||
|
||||
// Check the block is now air
|
||||
std::shared_ptr<Botcraft::World> world = bot->GetWorld();
|
||||
CHECK(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
const Botcraft::Blockstate* block = world->GetBlock(pos);
|
||||
return block == nullptr || block->IsAir();
|
||||
}, 5000));
|
||||
|
||||
const double elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 1000.0;
|
||||
// We are rather large here to compensate lags from delays in tree swapping/executing
|
||||
CHECK_THAT(elapsed_time, Catch::Matchers::WithinAbs(time_s, 0.2));
|
||||
}
|
||||
|
||||
TEST_CASE("dig pickaxe")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(1,0,1));
|
||||
const std::string& botname = bot->GetNetworkManager()->GetMyName();
|
||||
|
||||
const Botcraft::Position chest = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(0, 0, 2);
|
||||
const Botcraft::Position stone = chest + Botcraft::Position(1, 0, 0);
|
||||
const Botcraft::Position iron = stone + Botcraft::Position(1, 0, 0);
|
||||
|
||||
SECTION("no haste")
|
||||
{
|
||||
SECTION("golden pick")
|
||||
{
|
||||
GiveItem(bot, "golden_pickaxe", "Golden Pickaxe");
|
||||
TestDig(bot, chest, 3.75);
|
||||
TestDig(bot, stone, 0.2);
|
||||
TestDig(bot, iron, 2.1);
|
||||
}
|
||||
|
||||
SECTION("diamond pick")
|
||||
{
|
||||
GiveItem(bot, "diamond_pickaxe", "Diamond Pickaxe");
|
||||
TestDig(bot, chest, 3.75);
|
||||
TestDig(bot, stone, 0.3);
|
||||
TestDig(bot, iron, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("haste")
|
||||
{
|
||||
// Haste 2 is given using amplifier 1
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
MinecraftServer::GetInstance().SendLine("effect give " + botname + " haste 99999 1");
|
||||
#else
|
||||
MinecraftServer::GetInstance().SendLine("effect " + botname + " haste 99999 1");
|
||||
#endif
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: (?:Applied effect Haste|Given Haste \\(ID [0-9]+\\)(?: \\* [0-9]+)?) to " + botname + ".*", 5000);
|
||||
|
||||
CHECK(Botcraft::Utilities::WaitForCondition([&]() {
|
||||
for (const Botcraft::EntityEffect& effect : bot->GetLocalPlayer()->GetEffects())
|
||||
{
|
||||
if (effect.type == Botcraft::EntityEffectType::Haste)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
|
||||
SECTION("golden pick")
|
||||
{
|
||||
GiveItem(bot, "golden_pickaxe", "Golden Pickaxe");
|
||||
TestDig(bot, chest, 2.7);
|
||||
TestDig(bot, stone, 0.15);
|
||||
TestDig(bot, iron, 1.5);
|
||||
}
|
||||
|
||||
SECTION("diamond pick")
|
||||
{
|
||||
GiveItem(bot, "diamond_pickaxe", "Diamond Pickaxe");
|
||||
TestDig(bot, chest, 2.7);
|
||||
TestDig(bot, stone, 0.25);
|
||||
TestDig(bot, iron, 0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("dig underwater")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(1, 0, 1));
|
||||
const std::string& botname = bot->GetNetworkManager()->GetMyName();
|
||||
|
||||
const Botcraft::Position dirt = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(0, 0, 2);
|
||||
const Botcraft::Position stone = dirt + Botcraft::Position(1, 0, 0);
|
||||
const Botcraft::Position iron = stone + Botcraft::Position(1, 0, 0);
|
||||
|
||||
SECTION("aqua affinity")
|
||||
{
|
||||
GiveItem(bot, "diamond_pickaxe", "Diamond Pickaxe");
|
||||
#if PROTOCOL_VERSION < 393 /* < 1.13 */
|
||||
MinecraftServer::GetInstance().SendLine("replaceitem entity " + botname + " slot.armor.head minecraft:diamond_helmet 1 0 {ench:[{id:" + std::to_string(static_cast<int>(Botcraft::EnchantmentID::AquaAffinity)) + ",lvl:1}]}");
|
||||
#elif PROTOCOL_VERSION < 755 /* < 1.17 */
|
||||
MinecraftServer::GetInstance().SendLine("replaceitem entity " + botname + " armor.head minecraft:diamond_helmet{Enchantments:[{id:aqua_affinity,lvl:1}]} 1");
|
||||
#else
|
||||
MinecraftServer::GetInstance().SendLine("item replace entity " + botname + " armor.head with minecraft:diamond_helmet{Enchantments:[{id:aqua_affinity,lvl:1}]} 1");
|
||||
#endif
|
||||
CHECK(Botcraft::Utilities::WaitForCondition([&]() -> bool
|
||||
{
|
||||
return !bot->GetInventoryManager()->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HEAD_ARMOR).IsEmptySlot();
|
||||
}, 5000));
|
||||
|
||||
TestDig(bot, dirt, 0.75);
|
||||
TestDig(bot, stone, 0.3);
|
||||
TestDig(bot, iron, 0.95);
|
||||
}
|
||||
|
||||
SECTION("haste")
|
||||
{
|
||||
// Haste 2 is given using amplifier 1
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
MinecraftServer::GetInstance().SendLine("effect give " + botname + " haste 99999 1");
|
||||
#else
|
||||
MinecraftServer::GetInstance().SendLine("effect " + botname + " haste 99999 1");
|
||||
#endif
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: (?:Applied effect Haste|Given Haste \\(ID [0-9]+\\)(?: \\* [0-9]+)?) to " + botname + ".*", 5000);
|
||||
|
||||
CHECK(Botcraft::Utilities::WaitForCondition([&]() {
|
||||
for (const Botcraft::EntityEffect& effect : bot->GetLocalPlayer()->GetEffects())
|
||||
{
|
||||
if (effect.type == Botcraft::EntityEffectType::Haste)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
|
||||
SECTION("golden pick")
|
||||
{
|
||||
GiveItem(bot, "golden_pickaxe", "Golden Pickaxe");
|
||||
TestDig(bot, dirt, 2.7);
|
||||
TestDig(bot, stone, 0.7);
|
||||
TestDig(bot, iron, 7.45);
|
||||
}
|
||||
|
||||
SECTION("diamond pick")
|
||||
{
|
||||
GiveItem(bot, "diamond_pickaxe", "Diamond Pickaxe");
|
||||
TestDig(bot, dirt, 2.7);
|
||||
TestDig(bot, stone, 1.05);
|
||||
TestDig(bot, iron, 3.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("dig shears")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(1, 0, 1));
|
||||
const Botcraft::Position dirt = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(0, 0, 2);
|
||||
const Botcraft::Position leaves = dirt + Botcraft::Position(1, 0, 0);
|
||||
const Botcraft::Position cobweb = leaves + Botcraft::Position(1, 0, 0);
|
||||
|
||||
SECTION("shears")
|
||||
{
|
||||
GiveItem(bot, "shears", "Shears");
|
||||
TestDig(bot, dirt, 0.75);
|
||||
TestDig(bot, leaves, 0.0);
|
||||
TestDig(bot, cobweb, 0.4);
|
||||
}
|
||||
|
||||
SECTION("sword")
|
||||
{
|
||||
GiveItem(bot, "iron_sword", "Iron Sword");
|
||||
TestDig(bot, dirt, 0.75);
|
||||
TestDig(bot, leaves, 0.2);
|
||||
TestDig(bot, cobweb, 0.4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
#include "TestManager.hpp"
|
||||
#include "MinecraftServer.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <botcraft/Game/Entities/EntityManager.hpp>
|
||||
#include <botcraft/Game/Entities/entities/Entity.hpp>
|
||||
#include <botcraft/Game/Entities/entities/monster/CreeperEntity.hpp>
|
||||
#include <botcraft/Game/Inventory/InventoryManager.hpp>
|
||||
|
||||
#include <botcraft/AI/Tasks/EntitiesTasks.hpp>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
TEST_CASE("summoning")
|
||||
{
|
||||
const std::vector<std::string> types = {
|
||||
"cow",
|
||||
"horse",
|
||||
"villager",
|
||||
"witch"
|
||||
};
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot = SetupTestBot();
|
||||
const Botcraft::Vector3<double> pos = Botcraft::Vector3<double>(1.5, 0, 1.5) + TestManager::GetInstance().GetCurrentOffset();
|
||||
|
||||
for (const auto& t : types)
|
||||
{
|
||||
SECTION(t)
|
||||
{
|
||||
std::stringstream command;
|
||||
command
|
||||
<< "summon" << " "
|
||||
<< t << " "
|
||||
<< pos.x << " "
|
||||
<< pos.y << " "
|
||||
<< pos.z << " "
|
||||
<< "{NoAI:1b,PersistenceRequired:1b}";
|
||||
MinecraftServer::GetInstance().SendLine(command.str());
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: Summoned new.*", 5000);
|
||||
#else
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: Object successfully summoned.*", 5000);
|
||||
#endif
|
||||
std::shared_ptr<Botcraft::EntityManager> entity_manager = bot->GetEntityManager();
|
||||
std::shared_ptr<Botcraft::Entity> entity;
|
||||
// Wait for the entity to be registered on bot side
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
auto entities = entity_manager->GetEntities();
|
||||
for (const auto& [k, v] : *entities)
|
||||
{
|
||||
if (v->GetPosition().SqrDist(pos) < 0.2)
|
||||
{
|
||||
entity = v;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
REQUIRE(entity != nullptr);
|
||||
CHECK(entity->GetName() == t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("entity interact")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
const std::string& botname = bot->GetNetworkManager()->GetMyName();
|
||||
const Botcraft::Vector3<double> pos = Botcraft::Vector3<double>(1.5, 0, 1.5) + TestManager::GetInstance().GetCurrentOffset();
|
||||
|
||||
// Give flint and steel to bot
|
||||
MinecraftServer::GetInstance().SendLine("give " + botname + " flint_and_steel");
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: (?:Given|Gave 1) \\[Flint and Steel\\](?: \\* 1)? to " + botname + ".*", 5000);
|
||||
Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return !bot->GetInventoryManager()->GetHotbarSelected().IsEmptySlot();
|
||||
}, 5000);
|
||||
|
||||
// Wait for the entity to be registered on bot side
|
||||
std::shared_ptr<Botcraft::EntityManager> entity_manager = bot->GetEntityManager();
|
||||
std::shared_ptr<Botcraft::Entity> entity;
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
auto entities = entity_manager->GetEntities();
|
||||
for (const auto& [k, v] : *entities)
|
||||
{
|
||||
if (v->GetPosition().SqrDist(pos) < 0.2)
|
||||
{
|
||||
entity = v;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
REQUIRE(entity != nullptr);
|
||||
CHECK(entity->GetName() == "creeper");
|
||||
|
||||
bot->SyncAction(5000, Botcraft::InteractEntity, entity->GetEntityID(), Botcraft::Hand::Main, true);
|
||||
|
||||
// Wait for the creeper to be ignited
|
||||
std::shared_ptr<Botcraft::CreeperEntity> creeper = std::dynamic_pointer_cast<Botcraft::CreeperEntity>(entity);
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return creeper->GetDataIsIgnited();
|
||||
}, 5000));
|
||||
|
||||
// Wait for the creeper to explode
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return entity_manager->GetEntity(creeper->GetEntityID()) == nullptr;
|
||||
}, 5000));
|
||||
}
|
||||
|
||||
TEST_CASE("player names")
|
||||
{
|
||||
// Setup two bots and check their names are present in the "tab list"
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot = SetupTestBot();
|
||||
std::shared_ptr<Botcraft::EntityManager> entity_manager = bot->GetEntityManager();
|
||||
CHECK(bot->GetNetworkManager()->GetMyName() == bot->GetPlayerName(entity_manager->GetLocalPlayer()->GetUUID()));
|
||||
|
||||
const Botcraft::Vector3<double> bot_pos = entity_manager->GetLocalPlayer()->GetPosition();
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot2 = SetupTestBot();
|
||||
std::shared_ptr<Botcraft::Entity> bot2_entity;
|
||||
// Find bot2 in bot entities
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
for (const auto& [id, e] : *entity_manager->GetEntities())
|
||||
{
|
||||
if (id == bot2->GetLocalPlayer()->GetEntityID())
|
||||
{
|
||||
bot2_entity = e;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
// Check bot sees bot2 with it's name
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return bot->GetPlayerName(bot2_entity->GetUUID()) == bot2->GetNetworkManager()->GetMyName();
|
||||
}, 5000));
|
||||
|
||||
bot2->Disconnect();
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return bot->GetPlayerName(bot2_entity->GetUUID()) == "";
|
||||
}, 5000));
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
#include "TestManager.hpp"
|
||||
#include "MinecraftServer.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <botcraft/AI/Tasks/InventoryTasks.hpp>
|
||||
#include <botcraft/AI/Tasks/EntitiesTasks.hpp>
|
||||
|
||||
#include <botcraft/Game/Inventory/InventoryManager.hpp>
|
||||
#include <botcraft/Game/World/World.hpp>
|
||||
#include <botcraft/Game/Entities/EntityManager.hpp>
|
||||
#include <botcraft/Game/Entities/entities/Entity.hpp>
|
||||
#include <botcraft/Game/Entities/entities/item/ItemEntity.hpp>
|
||||
#include <botcraft/Game/Entities/entities/npc/VillagerEntity.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
|
||||
std::string GetItemName(const ProtocolCraft::Slot& slot)
|
||||
{
|
||||
if (slot.IsEmptySlot())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return Botcraft::AssetsManager::getInstance().GetItem(slot.GetItemID())->GetName();
|
||||
}
|
||||
|
||||
TEST_CASE("receive item")
|
||||
{
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot = SetupTestBot();
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:stick", "Stick", 1));
|
||||
CHECK(GiveItem(bot, "minecraft:stick", "Stick", 1));
|
||||
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
REQUIRE_FALSE(inventory_manager->GetHotbarSelected().IsEmptySlot());
|
||||
}
|
||||
|
||||
TEST_CASE("swap slots")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:stick", "Stick", 5));
|
||||
CHECK(GiveItem(bot, "minecraft:diamond_pickaxe", "Diamond Pickaxe", 1));
|
||||
|
||||
bot->SyncAction(5000, Botcraft::SwapItemsInContainer, Botcraft::Window::PLAYER_INVENTORY_INDEX, Botcraft::Window::INVENTORY_HOTBAR_START, Botcraft::Window::INVENTORY_HOTBAR_START + 1);
|
||||
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
CHECK(GetItemName(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START)) == "minecraft:diamond_pickaxe");
|
||||
CHECK(GetItemName(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START + 1)) == "minecraft:stick");
|
||||
CHECK(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START + 1).GetItemCount() == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("drop items")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:stick", "Stick", 5));
|
||||
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
const std::shared_ptr<Botcraft::EntityManager> entity_manager = bot->GetEntityManager();
|
||||
const Botcraft::Vector3<double> position = Botcraft::Vector3<double>(0.5, 0.0, 0.5) + TestManager::GetInstance().GetCurrentOffset();
|
||||
|
||||
SECTION("drop 1")
|
||||
{
|
||||
// Drop 1 stick
|
||||
bot->SyncAction(5000, Botcraft::DropItemsFromContainer, Botcraft::Window::PLAYER_INVENTORY_INDEX, Botcraft::Window::INVENTORY_HOTBAR_START, 4);
|
||||
|
||||
// We still have 4 sticks
|
||||
CHECK(GetItemName(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START)) == "minecraft:stick");
|
||||
CHECK(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START).GetItemCount() == 4);
|
||||
// There is a stick entity on the floor
|
||||
CHECK(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
auto entities = entity_manager->GetEntities();
|
||||
for (const auto& [id, e] : *entities)
|
||||
{
|
||||
// If this is an item entity and closer than 3.5 blocks
|
||||
if (e->GetType() == Botcraft::ItemEntity::GetClassType() &&
|
||||
e->GetPosition().SqrDist(position) < 12.25)
|
||||
{
|
||||
std::shared_ptr<Botcraft::ItemEntity> floor_items = std::dynamic_pointer_cast<Botcraft::ItemEntity>(e);
|
||||
return
|
||||
GetItemName(floor_items->GetDataItem()) == "minecraft:stick" &&
|
||||
floor_items->GetDataItem().GetItemCount() == 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
}
|
||||
SECTION("drop all")
|
||||
{
|
||||
// Drop all sticks
|
||||
bot->SyncAction(5000, Botcraft::DropItemsFromContainer, Botcraft::Window::PLAYER_INVENTORY_INDEX, Botcraft::Window::INVENTORY_HOTBAR_START, 0);
|
||||
|
||||
// We have nothing now
|
||||
CHECK(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START).IsEmptySlot());
|
||||
// There is a stick entity on the floor
|
||||
CHECK(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
auto entities = entity_manager->GetEntities();
|
||||
for (const auto& [id, e] : *entities)
|
||||
{
|
||||
// If this is an item entity and closer than 3.5 blocks
|
||||
if (e->GetType() == Botcraft::ItemEntity::GetClassType() &&
|
||||
e->GetPosition().SqrDist(position) < 12.25)
|
||||
{
|
||||
std::shared_ptr<Botcraft::ItemEntity> floor_items = std::dynamic_pointer_cast<Botcraft::ItemEntity>(e);
|
||||
return
|
||||
GetItemName(floor_items->GetDataItem()) == "minecraft:stick" &&
|
||||
floor_items->GetDataItem().GetItemCount() == 5;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("put one item")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:stick", "Stick", 5));
|
||||
|
||||
for (int i = 1; i < 5; ++i)
|
||||
{
|
||||
bot->SyncAction(5000, Botcraft::PutOneItemInContainerSlot, Botcraft::Window::PLAYER_INVENTORY_INDEX, Botcraft::Window::INVENTORY_HOTBAR_START, Botcraft::Window::INVENTORY_HOTBAR_START + i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
CHECK(GetItemName(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START + i)) == "minecraft:stick");
|
||||
CHECK(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START + i).GetItemCount() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("set in hand")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:stick", "Stick", 1));
|
||||
CHECK(GiveItem(bot, "minecraft:diamond_pickaxe", "Diamond Pickaxe", 1));
|
||||
|
||||
bot->SyncAction(5000, Botcraft::SetItemInHand, "minecraft:diamond_pickaxe", Botcraft::Hand::Right);
|
||||
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
{
|
||||
REQUIRE(GetItemName(inventory_manager->GetHotbarSelected()) == "minecraft:diamond_pickaxe");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("place block")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
const std::shared_ptr<Botcraft::World> world = bot->GetWorld();
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:diamond_block", "Block of Diamond", 1));
|
||||
Botcraft::Position pos;
|
||||
|
||||
SECTION("no mid air")
|
||||
{
|
||||
pos = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(1, 0, 1);
|
||||
bot->SyncAction(5000, Botcraft::PlaceBlock, "minecraft:diamond_block", pos, Botcraft::PlayerDiggingFace::Up, true, false);
|
||||
}
|
||||
SECTION("mid air")
|
||||
{
|
||||
pos = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(1, 1, 1);
|
||||
bot->SyncAction(5000, Botcraft::PlaceBlock, "minecraft:diamond_block", pos, Botcraft::PlayerDiggingFace::Up, true, true);
|
||||
}
|
||||
SECTION("automatic face detection")
|
||||
{
|
||||
pos = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(1, 0, 1);
|
||||
bot->SyncAction(5000, Botcraft::PlaceBlock, "minecraft:diamond_block", pos, std::nullopt, true, false);
|
||||
}
|
||||
|
||||
const Botcraft::Blockstate* block = world->GetBlock(pos);
|
||||
REQUIRE(block != nullptr);
|
||||
REQUIRE(block->GetName() == "minecraft:diamond_block");
|
||||
}
|
||||
|
||||
TEST_CASE("eat")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:golden_apple", "Golden Apple", 1));
|
||||
|
||||
bot->SyncAction(5000, Botcraft::Eat, "minecraft:golden_apple", true);
|
||||
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
// Check we don't have a golden apple in main or off hand
|
||||
REQUIRE(inventory_manager->GetHotbarSelected().IsEmptySlot());
|
||||
REQUIRE(inventory_manager->GetOffHand().IsEmptySlot());
|
||||
}
|
||||
|
||||
TEST_CASE("container")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
const Botcraft::Position chest = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(1, 0, 1);
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:stick", "Stick", 5));
|
||||
|
||||
bot->SyncAction(5000, Botcraft::OpenContainer, chest);
|
||||
short container_id = inventory_manager->GetFirstOpenedWindowId();
|
||||
std::shared_ptr<Botcraft::Window> container = inventory_manager->GetWindow(container_id);
|
||||
REQUIRE(container_id != -1);
|
||||
|
||||
SECTION("put 1")
|
||||
{
|
||||
bot->SyncAction(5000, Botcraft::PutOneItemInContainerSlot, container_id, container->GetFirstPlayerInventorySlot() + Botcraft::Window::INVENTORY_HOTBAR_START - Botcraft::Window::INVENTORY_STORAGE_START, 0);
|
||||
bot->SyncAction(5000, Botcraft::CloseContainer, container_id);
|
||||
#if PROTOCOL_VERSION < 755 /* < 1.17 */
|
||||
// If <1.17 we need to wait for the server to send the
|
||||
// updated player inventory content after closing the container
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
if (GetItemName(inventory_manager->GetHotbarSelected()) == "minecraft:stick" &&
|
||||
inventory_manager->GetHotbarSelected().GetItemCount() == 4)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
#else
|
||||
REQUIRE(GetItemName(inventory_manager->GetHotbarSelected()) == "minecraft:stick");
|
||||
REQUIRE(inventory_manager->GetHotbarSelected().GetItemCount() == 4);
|
||||
#endif
|
||||
container_id = -1;
|
||||
bot->SyncAction(5000, Botcraft::OpenContainer, chest);
|
||||
container_id = inventory_manager->GetFirstOpenedWindowId();
|
||||
container = inventory_manager->GetWindow(container_id);
|
||||
REQUIRE(container_id != -1);
|
||||
REQUIRE(GetItemName(container->GetSlot(0)) == "minecraft:stick");
|
||||
REQUIRE(container->GetSlot(0).GetItemCount() == 1);
|
||||
}
|
||||
SECTION("put all")
|
||||
{
|
||||
bot->SyncAction(5000, Botcraft::SwapItemsInContainer, container_id, container->GetFirstPlayerInventorySlot() + Botcraft::Window::INVENTORY_HOTBAR_START - Botcraft::Window::INVENTORY_STORAGE_START, 0);
|
||||
bot->SyncAction(5000, Botcraft::CloseContainer, container_id);
|
||||
#if PROTOCOL_VERSION < 755 /* < 1.17 */
|
||||
// If <1.17 we need to wait for the server to send the
|
||||
// updated player inventory content after closing the container
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
if (inventory_manager->GetHotbarSelected().IsEmptySlot())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
#else
|
||||
REQUIRE(inventory_manager->GetHotbarSelected().IsEmptySlot());
|
||||
#endif
|
||||
container_id = -1;
|
||||
bot->SyncAction(5000, Botcraft::OpenContainer, chest);
|
||||
container_id = inventory_manager->GetFirstOpenedWindowId();
|
||||
container = inventory_manager->GetWindow(container_id);
|
||||
REQUIRE(container_id != -1);
|
||||
REQUIRE(GetItemName(container->GetSlot(0)) == "minecraft:stick");
|
||||
REQUIRE(container->GetSlot(0).GetItemCount() == 5);
|
||||
}
|
||||
|
||||
bot->SyncAction(5000, Botcraft::CloseContainer, container_id);
|
||||
REQUIRE(inventory_manager->GetFirstOpenedWindowId() == -1);
|
||||
}
|
||||
|
||||
#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
|
||||
TEST_CASE("trade")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
const std::shared_ptr<Botcraft::EntityManager> entity_manager = bot->GetEntityManager();
|
||||
|
||||
const Botcraft::Vector3<double> pos = Botcraft::Vector3<double>(1.5, 0, 1.5) + TestManager::GetInstance().GetCurrentOffset();
|
||||
|
||||
std::shared_ptr<Botcraft::Entity> entity;
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
auto entities = entity_manager->GetEntities();
|
||||
for (const auto& [k, v] : *entities)
|
||||
{
|
||||
if (v->GetPosition().SqrDist(pos) < 0.2 &&
|
||||
v->GetType() == Botcraft::VillagerEntity::GetClassType())
|
||||
{
|
||||
entity = v;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
REQUIRE(entity != nullptr);
|
||||
REQUIRE(GiveItem(bot, "minecraft:stick", "Stick", 1));
|
||||
|
||||
bot->SyncAction(5000, Botcraft::InteractEntity, entity->GetEntityID(), Botcraft::Hand::Right, true);
|
||||
bot->SyncAction(5000, Botcraft::TradeName, "minecraft:stick", false, -1);
|
||||
bot->SyncAction(5000, Botcraft::TradeName, "minecraft:enchanted_book", true, -1);
|
||||
bot->SyncAction(5000, Botcraft::CloseContainer, -1);
|
||||
|
||||
// Wait for the player inventory to update
|
||||
CHECK(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
return !inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_STORAGE_START).IsEmptySlot();
|
||||
}, 5000));
|
||||
|
||||
bot->SyncAction(5000, Botcraft::SetItemInHand, "minecraft:enchanted_book", Botcraft::Hand::Right);
|
||||
REQUIRE(GetItemName(inventory_manager->GetHotbarSelected()) == "minecraft:enchanted_book");
|
||||
REQUIRE(inventory_manager->GetHotbarSelected().GetItemCount() == 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("craft")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
const Botcraft::Position table = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(1, 0, 1);
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:diamond_block", "Block of Diamond", 1));
|
||||
|
||||
const std::array<std::string, 3> empty = { "", "", "" };
|
||||
const std::array<std::array<std::string, 3>, 3> decraft_recipe = {
|
||||
empty,
|
||||
empty,
|
||||
{"minecraft:diamond_block", "", ""}
|
||||
};
|
||||
|
||||
bot->SyncAction(5000, Botcraft::CraftNamed, decraft_recipe, true);
|
||||
REQUIRE(GetItemName(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_STORAGE_START)) == "minecraft:diamond");
|
||||
REQUIRE(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_STORAGE_START).GetItemCount() == 9);
|
||||
|
||||
bot->SyncAction(5000, Botcraft::OpenContainer, table);
|
||||
short container_id = inventory_manager->GetFirstOpenedWindowId();
|
||||
std::shared_ptr<Botcraft::Window> container = inventory_manager->GetWindow(container_id);
|
||||
|
||||
REQUIRE(container_id != -1);
|
||||
|
||||
const std::array<std::string, 3> diamond_line = { "minecraft:diamond", "minecraft:diamond", "minecraft:diamond" };
|
||||
const std::array<std::array<std::string, 3>, 3> craft_recipe = {
|
||||
diamond_line,
|
||||
diamond_line,
|
||||
diamond_line
|
||||
};
|
||||
|
||||
bot->SyncAction(5000, Botcraft::CraftNamed, craft_recipe, false);
|
||||
bot->SyncAction(5000, Botcraft::CloseContainer, -1);
|
||||
#if PROTOCOL_VERSION < 755 /* < 1.17 */
|
||||
// If <1.17 we need to wait for the server to send the
|
||||
// updated player inventory content after closing the container
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
if (GetItemName(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_STORAGE_START)) == "minecraft:diamond_block" &&
|
||||
inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_STORAGE_START).GetItemCount() == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
#else
|
||||
REQUIRE(GetItemName(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_STORAGE_START)) == "minecraft:diamond_block");
|
||||
REQUIRE(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_STORAGE_START).GetItemCount() == 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("sort inventory")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>();
|
||||
const std::shared_ptr<Botcraft::InventoryManager> inventory_manager = bot->GetInventoryManager();
|
||||
|
||||
CHECK(GiveItem(bot, "minecraft:stick", "Stick", 5));
|
||||
|
||||
for (int i = 1; i < 5; ++i)
|
||||
{
|
||||
bot->SyncAction(5000, Botcraft::PutOneItemInContainerSlot, Botcraft::Window::PLAYER_INVENTORY_INDEX, Botcraft::Window::INVENTORY_HOTBAR_START, Botcraft::Window::INVENTORY_HOTBAR_START + i);
|
||||
}
|
||||
|
||||
bot->SyncAction(5000, Botcraft::SortInventory);
|
||||
|
||||
CHECK(GetItemName(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START)) == "minecraft:stick");
|
||||
CHECK(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START).GetItemCount() == 5);
|
||||
|
||||
for (int i = 1; i < 5; ++i)
|
||||
{
|
||||
CHECK(inventory_manager->GetPlayerInventory()->GetSlot(Botcraft::Window::INVENTORY_HOTBAR_START + i).IsEmptySlot());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
#include "TestManager.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include <botcraft/AI/SimpleBehaviourClient.hpp>
|
||||
#include <botcraft/AI/Tasks/PathfindingTask.hpp>
|
||||
|
||||
bool SameBlock(const Botcraft::Vector3<double>& p1, const Botcraft::Vector3<double>& p2)
|
||||
{
|
||||
return std::floor(p1.x) == std::floor(p2.x) &&
|
||||
std::floor(p1.y) == std::floor(p2.y) &&
|
||||
std::floor(p1.z) == std::floor(p2.z);
|
||||
}
|
||||
|
||||
TEST_CASE("simple pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(2.5, 2.01, 2.5));
|
||||
|
||||
std::vector<std::pair<std::string, Botcraft::Vector3<double>>> directions = {
|
||||
{"south", Botcraft::Position(0, 0, 2)},
|
||||
{"north", Botcraft::Position(0, 0, -2)},
|
||||
{"east", Botcraft::Position(2, 0, 0)},
|
||||
{"west", Botcraft::Position(-2, 0, 0)},
|
||||
};
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
for (size_t i = 0; i < directions.size(); ++i)
|
||||
{
|
||||
SECTION(directions[i].first)
|
||||
{
|
||||
bot->SyncAction(5000, Botcraft::GoTo, Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z)) + directions[i].second, 0, 0, 0, false, false, 1.0f);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + directions[i].second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("jump pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(1.5, 1.01, 1.5));
|
||||
|
||||
std::vector<std::pair<std::string, Botcraft::Vector3<double>>> directions = {
|
||||
{"south", Botcraft::Position(0, 1, 1)},
|
||||
{"north", Botcraft::Position(0, 1, -1)},
|
||||
{"east", Botcraft::Position(1, 1, 0)},
|
||||
{"west", Botcraft::Position(-1, 1, 0)},
|
||||
};
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
for (size_t i = 0; i < directions.size(); ++i)
|
||||
{
|
||||
SECTION(directions[i].first)
|
||||
{
|
||||
bot->SyncAction(5000, Botcraft::GoTo, Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z)) + directions[i].second, 0, 0, 0, true, false, 1.0f);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + directions[i].second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("gap pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(2.5, 2.01, 2.5));
|
||||
|
||||
std::vector<std::pair<std::string, Botcraft::Vector3<double>>> directions = {
|
||||
{"south", Botcraft::Position(0, 0, 2)},
|
||||
{"north", Botcraft::Position(0, 0, -2)},
|
||||
{"east", Botcraft::Position(2, 0, 0)},
|
||||
{"west", Botcraft::Position(-2, 0, 0)},
|
||||
};
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
for (size_t i = 0; i < directions.size(); ++i)
|
||||
{
|
||||
SECTION(directions[i].first)
|
||||
{
|
||||
bot->SyncAction(5000, Botcraft::GoTo, Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z)) + directions[i].second, 0, 0, 0, true, false, 1.0f);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + directions[i].second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("climb pathfinding")
|
||||
{
|
||||
std::vector<std::pair<std::string, Botcraft::Vector3<double>>> directions = {
|
||||
{"south", Botcraft::Position(0, 4, -2)},
|
||||
{"north", Botcraft::Position(0, 4, 2)},
|
||||
{"east", Botcraft::Position(-2, 4, 0)},
|
||||
{"west", Botcraft::Position(2, 4, 0)},
|
||||
};
|
||||
const Botcraft::Position& current_offset = TestManager::GetInstance().GetCurrentOffset();
|
||||
|
||||
for (size_t i = 0; i < directions.size(); ++i)
|
||||
{
|
||||
SECTION(directions[i].first)
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(2.5, 5.01, 2.5) - directions[i].second);
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]() -> bool
|
||||
{
|
||||
return local_player->GetOnGround();
|
||||
}, 2000));
|
||||
|
||||
bot->SyncAction(10000, Botcraft::GoTo, Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z)) + directions[i].second, 0, 0, 0, true, false, 1.0f);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + directions[i].second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("fall pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(2.5, 5.01, 2.5));
|
||||
|
||||
std::vector<std::pair<std::string, Botcraft::Vector3<double>>> directions = {
|
||||
{"south", Botcraft::Position(0, -4, 2)},
|
||||
{"north", Botcraft::Position(0, -4, -2)},
|
||||
{"east", Botcraft::Position(2, -4, 0)},
|
||||
{"west", Botcraft::Position(-2, -4, 0)},
|
||||
};
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
for (size_t i = 0; i < directions.size(); ++i)
|
||||
{
|
||||
SECTION(directions[i].first)
|
||||
{
|
||||
bot->SyncAction(10000, Botcraft::GoTo, Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z)) + directions[i].second, 0, 0, 0, true, false, 1.0f);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + directions[i].second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("full pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot;
|
||||
SECTION("survival")
|
||||
{
|
||||
bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(0.0, 1.01, 0.0));
|
||||
}
|
||||
SECTION("creative")
|
||||
{
|
||||
bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(0.0, 1.01, 0.0), Botcraft::GameType::Creative);
|
||||
}
|
||||
|
||||
const Botcraft::Position delta(2, 3, 24);
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
bot->SyncAction(120000, Botcraft::GoTo, Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z)) + delta, 0, 0, 0, true, false, 1.0f);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + delta));
|
||||
CHECK_THAT(local_player->GetHealth(), Catch::Matchers::WithinAbs(20.0, 0.01));
|
||||
}
|
||||
|
||||
TEST_CASE("hazardous pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(1.0, 1.01, 0.0));
|
||||
const Botcraft::Position delta(0, 4, 2);
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
bot->SyncAction(60000, Botcraft::GoTo, Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z)) + delta, 0, 0, 0, true, false, 1.0f);
|
||||
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + delta));
|
||||
CHECK_THAT(local_player->GetHealth(), Catch::Matchers::WithinAbs(20.0, 0.01));
|
||||
}
|
||||
|
||||
TEST_CASE("water walking pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(2.0, 2.01, 0.0));
|
||||
const Botcraft::Position delta(0, 0, 6);
|
||||
const Botcraft::Position string_control_check_delta(0, 2, 3);
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
std::shared_ptr<Botcraft::World> world = bot->GetWorld();
|
||||
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
Botcraft::Position init_position_int = Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z));
|
||||
|
||||
bot->SyncAction(60000, Botcraft::GoTo, init_position_int + delta, 0, 0, 0, true, false, 1.0f);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + delta));
|
||||
const Botcraft::Blockstate* should_not_be_air = world->GetBlock(init_position_int + string_control_check_delta);
|
||||
REQUIRE_FALSE(should_not_be_air == nullptr);
|
||||
CHECK_FALSE(should_not_be_air->IsAir());
|
||||
}
|
||||
|
||||
TEST_CASE("ladder walking pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(2.0, 2.01, 0.0));
|
||||
const Botcraft::Position delta(0, 0, 6);
|
||||
const Botcraft::Position string_control_check_delta(0, 2, 3);
|
||||
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
std::shared_ptr<Botcraft::World> world = bot->GetWorld();
|
||||
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
const Botcraft::Position init_position_int = Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z));
|
||||
|
||||
bot->SyncAction(60000, Botcraft::GoTo, init_position_int + delta, 0, 0, 0, true, false, 1.0f);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + delta));
|
||||
const Botcraft::Blockstate* should_not_be_air = world->GetBlock(init_position_int + string_control_check_delta);
|
||||
REQUIRE_FALSE(should_not_be_air == nullptr);
|
||||
CHECK_FALSE(should_not_be_air->IsAir());
|
||||
}
|
||||
|
||||
TEST_CASE("speed pathfinding")
|
||||
{
|
||||
std::unique_ptr<Botcraft::SimpleBehaviourClient> bot = SetupTestBot<Botcraft::SimpleBehaviourClient>(Botcraft::Vector3<double>(0.0, 1.01, 0.0));
|
||||
std::shared_ptr<Botcraft::LocalPlayer> local_player = bot->GetLocalPlayer();
|
||||
const std::string& botname = bot->GetNetworkManager()->GetMyName();
|
||||
|
||||
bool sprint = false;
|
||||
int speed_effect = 0;
|
||||
float botcraft_speed_factor = 1.0f;
|
||||
// Expected time is an estimate to check consistency with previous versions. It's not consistent with distance/theoretical speed
|
||||
// This is mainly due to the way we travel from block to block instead of from start to goal directly
|
||||
float expected_time_s = 0.0f;
|
||||
|
||||
SECTION("no speed")
|
||||
{
|
||||
speed_effect = 0;
|
||||
botcraft_speed_factor = 1.0f;
|
||||
SECTION("walk")
|
||||
{
|
||||
sprint = false;
|
||||
expected_time_s = 5.7f;
|
||||
}
|
||||
SECTION("sprint")
|
||||
{
|
||||
sprint = true;
|
||||
expected_time_s = 4.5f;
|
||||
}
|
||||
}
|
||||
SECTION("botcraft cheaty speed")
|
||||
{
|
||||
speed_effect = 0;
|
||||
botcraft_speed_factor = 1.5f;
|
||||
SECTION("walk")
|
||||
{
|
||||
sprint = false;
|
||||
expected_time_s = 3.9f;
|
||||
}
|
||||
SECTION("sprint")
|
||||
{
|
||||
sprint = true;
|
||||
expected_time_s = 3.1f;
|
||||
}
|
||||
}
|
||||
SECTION("speed I")
|
||||
{
|
||||
speed_effect = 1;
|
||||
botcraft_speed_factor = 1.0f;
|
||||
SECTION("walk")
|
||||
{
|
||||
sprint = false;
|
||||
expected_time_s = 4.8f;
|
||||
}
|
||||
SECTION("sprint")
|
||||
{
|
||||
sprint = true;
|
||||
expected_time_s = 3.8f;
|
||||
}
|
||||
}
|
||||
SECTION("speed II")
|
||||
{
|
||||
speed_effect = 2;
|
||||
botcraft_speed_factor = 1.0f;
|
||||
SECTION("walk")
|
||||
{
|
||||
sprint = false;
|
||||
expected_time_s = 4.1f;
|
||||
}
|
||||
SECTION("sprint")
|
||||
{
|
||||
sprint = true;
|
||||
expected_time_s = 3.2f;
|
||||
}
|
||||
}
|
||||
|
||||
if (speed_effect > 0)
|
||||
{
|
||||
// Speed N is given using N-1
|
||||
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
||||
MinecraftServer::GetInstance().SendLine("effect give " + botname + " speed 99999 " + std::to_string(speed_effect - 1));
|
||||
#else
|
||||
MinecraftServer::GetInstance().SendLine("effect " + botname + " speed 99999 " + std::to_string(speed_effect - 1));
|
||||
#endif
|
||||
MinecraftServer::GetInstance().WaitLine(".*?: (?:Applied effect Speed|Given Speed \\(ID [0-9]+\\)(?: \\* [0-9]+)?) to " + botname + ".*", 5000);
|
||||
|
||||
CHECK(Botcraft::Utilities::WaitForCondition([&]() {
|
||||
for (const Botcraft::EntityEffect& effect : local_player->GetEffects())
|
||||
{
|
||||
if (effect.type == Botcraft::EntityEffectType::Speed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 5000));
|
||||
}
|
||||
|
||||
const Botcraft::Position delta(0, 0, 24);
|
||||
|
||||
const Botcraft::Vector3<double> init_position = local_player->GetPosition();
|
||||
|
||||
const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
||||
bot->SyncAction(10000, Botcraft::GoTo, Botcraft::Position(std::floor(init_position.x), std::floor(init_position.y), std::floor(init_position.z)) + delta, 0, 0, 0, true, sprint, botcraft_speed_factor);
|
||||
CHECK(SameBlock(local_player->GetPosition(), init_position + delta));
|
||||
const float time_taken = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() / 1000.0f;
|
||||
CHECK_THAT(time_taken, Catch::Matchers::WithinAbs(expected_time_s, 0.2));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
#include "TestManager.hpp"
|
||||
#include "MinecraftServer.hpp"
|
||||
|
||||
#include <botcraft/Game/ManagersClient.hpp>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
TEST_CASE("current offset")
|
||||
{
|
||||
CHECK(TestManager::GetInstance().GetCurrentOffset() != Botcraft::Position());
|
||||
}
|
||||
|
||||
TEST_CASE("commands")
|
||||
{
|
||||
MinecraftServer::GetInstance().SendLine("say hello");
|
||||
std::vector<std::string> captured;
|
||||
// \\w because we want the regex string to be \w
|
||||
REQUIRE_NOTHROW(captured = MinecraftServer::GetInstance().WaitLine(".*: (?:\\[Not Secure\\] )?[[<]Server[>\\]] \\b(\\w+)\\b.*", 5000));
|
||||
REQUIRE(captured.size() == 2);
|
||||
CHECK(captured[0].substr(captured[0].size() - 5) == "hello");
|
||||
CHECK(captured[1] == "hello");
|
||||
}
|
||||
|
||||
TEST_CASE("create block")
|
||||
{
|
||||
const Botcraft::Position anvil_position = TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(1, 0, 1);
|
||||
REQUIRE_NOTHROW(TestManager::GetInstance().SetBlock("anvil", anvil_position));
|
||||
}
|
||||
|
||||
TEST_CASE("create book")
|
||||
{
|
||||
const Botcraft::Position& pos = TestManager::GetInstance().GetCurrentOffset();
|
||||
#if PROTOCOL_VERSION < 477 /* < 1.14 */
|
||||
CHECK_NOTHROW(TestManager::GetInstance().SetBlock("anvil", pos + Botcraft::Position(1, 0, 1)));
|
||||
#endif
|
||||
REQUIRE_NOTHROW(TestManager::GetInstance().CreateBook(pos + Botcraft::Position(1, 0, 0), { "hello", "", "hello page 3" }, "north", "Title", "author", { "desc1", "desc2" }));
|
||||
}
|
||||
|
||||
TEST_CASE("teleport bot")
|
||||
{
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot;
|
||||
std::string botname;
|
||||
REQUIRE_NOTHROW(bot = TestManager::GetInstance().GetBot(botname, Botcraft::GameType::Adventure));
|
||||
CHECK_NOTHROW(TestManager::GetInstance().Teleport(botname, TestManager::GetInstance().GetCurrentOffset() + Botcraft::Vector3<double>(1, 2, 1)));
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
#include "TestManager.hpp"
|
||||
#include "MinecraftServer.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <botcraft/Game/AssetsManager.hpp>
|
||||
|
||||
#if PROTOCOL_VERSION > 760 /* > 1.19.2 */
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
|
||||
TEST_CASE("block names")
|
||||
{
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot = SetupTestBot();
|
||||
|
||||
std::vector<std::pair<Botcraft::Position, std::string> > pos_names = {
|
||||
#if PROTOCOL_VERSION < 393 /* < 1.13 */
|
||||
{Botcraft::Position(0, 0, 1), "minecraft:stained_glass"},
|
||||
{Botcraft::Position(1, 0, 0), "minecraft:magma"},
|
||||
{Botcraft::Position(1, 0, 1), "minecraft:wool"},
|
||||
{Botcraft::Position(1, 0, 2), "minecraft:melon_block"},
|
||||
#else
|
||||
{Botcraft::Position(0, 0, 1), "minecraft:white_stained_glass"},
|
||||
{Botcraft::Position(1, 0, 0), "minecraft:magma_block"},
|
||||
{Botcraft::Position(1, 0, 1), "minecraft:pink_wool"},
|
||||
{Botcraft::Position(1, 0, 2), "minecraft:melon"},
|
||||
#endif
|
||||
{Botcraft::Position(0, 0, 0), "minecraft:cobblestone"},
|
||||
{Botcraft::Position(0, 0, 2), "minecraft:bedrock"},
|
||||
{Botcraft::Position(0, 0, 3), "minecraft:bookshelf"},
|
||||
{Botcraft::Position(1, 0, 3), "minecraft:glass"}
|
||||
};
|
||||
const Botcraft::Position base_position = TestManager::GetInstance().GetCurrentOffset();
|
||||
for (size_t i = 0; i < pos_names.size(); ++i)
|
||||
{
|
||||
const Botcraft::Blockstate* block;
|
||||
block = bot->GetWorld()->GetBlock(base_position + pos_names[i].first);
|
||||
CHECK(block != nullptr);
|
||||
if (block)
|
||||
{
|
||||
CHECK(block->GetName() == pos_names[i].second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("block states")
|
||||
{
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot = SetupTestBot();
|
||||
|
||||
std::vector<std::tuple<Botcraft::Position, std::string, std::string, std::string> > pos_block_variables_values = {
|
||||
#if PROTOCOL_VERSION < 393 /* < 1.13 */
|
||||
{Botcraft::Position(0, 0, 0), "minecraft:stone_slab", "half", "bottom"},
|
||||
{Botcraft::Position(0, 0, 1), "minecraft:stone_slab", "half", "top"},
|
||||
#elif PROTOCOL_VERSION < 477 /* < 1.14 */
|
||||
{Botcraft::Position(0, 0, 0), "minecraft:stone_slab", "type", "bottom"},
|
||||
{Botcraft::Position(0, 0, 1), "minecraft:stone_slab", "type", "top"},
|
||||
#else
|
||||
{Botcraft::Position(0, 0, 0), "minecraft:smooth_stone_slab", "type", "bottom"},
|
||||
{Botcraft::Position(0, 0, 1), "minecraft:smooth_stone_slab", "type", "top"},
|
||||
#endif
|
||||
#if PROTOCOL_VERSION < 393 /* < 1.13 */
|
||||
{Botcraft::Position(0, 0, 2), "minecraft:redstone_torch", "facing", "up"},
|
||||
{Botcraft::Position(1, 0, 1), "minecraft:unlit_redstone_torch", "facing", "south"}
|
||||
#else
|
||||
{Botcraft::Position(0, 0, 2), "minecraft:redstone_torch", "lit", "true"},
|
||||
{Botcraft::Position(1, 0, 1), "minecraft:redstone_wall_torch", "facing", "south"},
|
||||
{Botcraft::Position(1, 0, 1), "minecraft:redstone_wall_torch", "lit", "false"}
|
||||
#endif
|
||||
};
|
||||
const Botcraft::Position base_position = TestManager::GetInstance().GetCurrentOffset();
|
||||
for (size_t i = 0; i < pos_block_variables_values.size(); ++i)
|
||||
{
|
||||
const Botcraft::Blockstate* block;
|
||||
block = bot->GetWorld()->GetBlock(base_position + std::get<0>(pos_block_variables_values[i]));
|
||||
CHECK(block != nullptr);
|
||||
if (block)
|
||||
{
|
||||
CHECK(block->GetName() == std::get<1>(pos_block_variables_values[i]));
|
||||
CHECK(block->GetVariableValue(std::get<2>(pos_block_variables_values[i])) == std::get<3>(pos_block_variables_values[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Biome names is sometimes wrong in 1.19.4 because of additional cherry_grove that
|
||||
// is only present when experimental is activated
|
||||
#if PROTOCOL_VERSION == 762 /* 1.19.4 */
|
||||
TEST_CASE("biomes", "[!mayfail]")
|
||||
#else
|
||||
TEST_CASE("biomes")
|
||||
#endif
|
||||
{
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot = SetupTestBot();
|
||||
|
||||
SECTION("base biome")
|
||||
{
|
||||
const Botcraft::Biome* biome;
|
||||
REQUIRE_NOTHROW(biome = bot->GetWorld()->GetBiome(TestManager::GetInstance().GetCurrentOffset()));
|
||||
REQUIRE(biome != nullptr);
|
||||
CHECK(biome->GetName() == "plains");
|
||||
}
|
||||
#if PROTOCOL_VERSION > 760 /* > 1.19.2 */
|
||||
SECTION("changed biome")
|
||||
{
|
||||
const Botcraft::Position start_pos = TestManager::GetInstance().GetCurrentOffset();
|
||||
const Botcraft::Position end_pos = start_pos + Botcraft::Position(2, 2, 2);
|
||||
std::stringstream command;
|
||||
command
|
||||
<< "fillbiome" << " "
|
||||
<< start_pos.x << " "
|
||||
<< start_pos.y << " "
|
||||
<< start_pos.z << " "
|
||||
<< end_pos.x << " "
|
||||
<< end_pos.y << " "
|
||||
<< end_pos.z << " "
|
||||
<< "desert";
|
||||
MinecraftServer::GetInstance().SendLine(command.str());
|
||||
MinecraftServer::GetInstance().WaitLine(".* biome (?:entry/)?entries set between .*", 5000);
|
||||
|
||||
const Botcraft::Biome* biome;
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
biome = bot->GetWorld()->GetBiome(start_pos);
|
||||
return biome != nullptr && biome->GetName() == "desert";
|
||||
}, 5000));
|
||||
REQUIRE(Botcraft::Utilities::WaitForCondition([&]()
|
||||
{
|
||||
biome = bot->GetWorld()->GetBiome(end_pos);
|
||||
return biome != nullptr && biome->GetName() == "desert";
|
||||
}, 5000));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if PROTOCOL_VERSION < 763 /* < 1.20 */
|
||||
TEST_CASE("block entity")
|
||||
#else
|
||||
TEST_CASE("block entity 1_20")
|
||||
#endif
|
||||
{
|
||||
std::unique_ptr<Botcraft::ManagersClient> bot = SetupTestBot();
|
||||
|
||||
ProtocolCraft::NBT::Value nbt;
|
||||
|
||||
REQUIRE_NOTHROW(nbt = bot->GetWorld()->GetBlockEntityData(TestManager::GetInstance().GetCurrentOffset() + Botcraft::Position(1, 0, 1)));
|
||||
REQUIRE(nbt.HasData());
|
||||
|
||||
std::vector<std::string> expected_lines = { "Hello", "world", "", "!" };
|
||||
#if PROTOCOL_VERSION > 762 /* > 1.19.4 */
|
||||
std::vector<std::string> expected_lines_back = { "Hello", "back", "", "!" };
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < expected_lines.size(); ++i)
|
||||
{
|
||||
#if PROTOCOL_VERSION < 763 /* < 1.20 */
|
||||
const std::string& line = nbt["Text" + std::to_string(i+1)].get<std::string>();
|
||||
const ProtocolCraft::Json::Value content = ProtocolCraft::Json::Parse(line);
|
||||
CHECK(content["text"].get_string() == expected_lines[i]);
|
||||
#else
|
||||
const std::string& front_line = nbt["front_text"]["messages"].as_list_of<std::string>()[i];
|
||||
const std::string& back_line = nbt["back_text"]["messages"].as_list_of<std::string>()[i];
|
||||
|
||||
#if PROTOCOL_VERSION < 765 /* < 1.20.3 */
|
||||
const ProtocolCraft::Json::Value front_content = ProtocolCraft::Json::Parse(front_line);
|
||||
const ProtocolCraft::Json::Value back_content = ProtocolCraft::Json::Parse(back_line);
|
||||
CHECK(front_content["text"].get_string() == expected_lines[i]);
|
||||
CHECK(back_content["text"].get_string() == expected_lines_back[i]);
|
||||
#else
|
||||
CHECK(front_line == "\"" + expected_lines[i] + "\"");
|
||||
CHECK(back_line == "\"" + expected_lines_back[i] + "\"");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user