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,41 @@
|
||||
project(botcraft_tests)
|
||||
|
||||
set(SRC_FILES
|
||||
src/aabb.cpp
|
||||
src/behaviour_tree.cpp
|
||||
src/blackboard.cpp
|
||||
src/blockstate.cpp
|
||||
src/world.cpp
|
||||
|
||||
src/init.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Catch2::Catch2WithMain)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE botcraft)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Tests)
|
||||
|
||||
# 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)
|
||||
catch_discover_tests(${PROJECT_NAME} WORKING_DIRECTORY "${BOTCRAFT_OUTPUT_DIR}/bin")
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
#include <botcraft/Game/Physics/AABB.hpp>
|
||||
|
||||
using namespace Botcraft;
|
||||
|
||||
TEST_CASE("Constructor")
|
||||
{
|
||||
AABB aabb(Vector3<double>(0.5, 0.5, 0.5), Vector3<double>(0.5, 0.5, 0.5));
|
||||
|
||||
REQUIRE_THAT(aabb.GetMin().x, Catch::Matchers::WithinAbs(0.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMin().y, Catch::Matchers::WithinAbs(0.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMin().z, Catch::Matchers::WithinAbs(0.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().x, Catch::Matchers::WithinAbs(1.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().y, Catch::Matchers::WithinAbs(1.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().z, Catch::Matchers::WithinAbs(1.0, 1e-8));
|
||||
}
|
||||
|
||||
TEST_CASE("Translate")
|
||||
{
|
||||
AABB aabb(Vector3<double>(0.5, 0.5, 0.5), Vector3<double>(0.5, 0.5, 0.5));
|
||||
aabb.Translate(Vector3<double>(1.0, 1.0, 1.0));
|
||||
|
||||
REQUIRE_THAT(aabb.GetMin().x, Catch::Matchers::WithinAbs(1.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMin().y, Catch::Matchers::WithinAbs(1.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMin().z, Catch::Matchers::WithinAbs(1.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().x, Catch::Matchers::WithinAbs(2.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().y, Catch::Matchers::WithinAbs(2.0, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().z, Catch::Matchers::WithinAbs(2.0, 1e-8));
|
||||
}
|
||||
|
||||
TEST_CASE("Inflate")
|
||||
{
|
||||
auto inflate = GENERATE(0.1, -0.1, 1e-6, -1e-6);
|
||||
AABB aabb(Vector3<double>(0.5, 0.5, 0.5), Vector3<double>(0.5, 0.5, 0.5));
|
||||
aabb.Inflate(inflate);
|
||||
|
||||
REQUIRE_THAT(aabb.GetMin().x, Catch::Matchers::WithinAbs(0.0 - inflate, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMin().y, Catch::Matchers::WithinAbs(0.0 - inflate, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMin().z, Catch::Matchers::WithinAbs(0.0 - inflate, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().x, Catch::Matchers::WithinAbs(1.0 + inflate, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().y, Catch::Matchers::WithinAbs(1.0 + inflate, 1e-8));
|
||||
REQUIRE_THAT(aabb.GetMax().z, Catch::Matchers::WithinAbs(1.0 + inflate, 1e-8));
|
||||
}
|
||||
@@ -0,0 +1,655 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
#include <botcraft/AI/BehaviourTree.hpp>
|
||||
|
||||
using namespace Botcraft;
|
||||
|
||||
Status TestLeaf(int& c)
|
||||
{
|
||||
c += 1;
|
||||
return Status::Success;
|
||||
}
|
||||
|
||||
Status TestLeafWithArg(int& c, const int i)
|
||||
{
|
||||
c += i;
|
||||
return Status::Success;
|
||||
}
|
||||
|
||||
TEST_CASE("LeafTree")
|
||||
{
|
||||
int i = 0;
|
||||
SECTION("Function")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.leaf(TestLeaf);
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 1);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
}
|
||||
|
||||
SECTION("NamedFunction")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.leaf("leaf", TestLeaf);
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 1);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
}
|
||||
|
||||
SECTION("FunctionWithArg")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.leaf(TestLeafWithArg, 2);
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 4);
|
||||
}
|
||||
|
||||
SECTION("NamedFunctionWithArg")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.leaf("leaf", TestLeafWithArg, 2);
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 4);
|
||||
}
|
||||
|
||||
SECTION("std::bind")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.leaf(std::bind(TestLeafWithArg, std::placeholders::_1, 2));
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 4);
|
||||
}
|
||||
|
||||
SECTION("Namedstd::bind")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.leaf("leaf", std::bind(TestLeafWithArg, std::placeholders::_1, 2));
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 4);
|
||||
}
|
||||
|
||||
SECTION("Lambda")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.leaf([](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 1);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
}
|
||||
|
||||
SECTION("NamedLambda")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.leaf("leaf", [](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 1);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Context>
|
||||
class CustomDecorator : public Decorator<Context>
|
||||
{
|
||||
using Decorator<Context>::Decorator;
|
||||
public:
|
||||
virtual Status TickImpl(Context& context) const override
|
||||
{
|
||||
this->TickChild(context);
|
||||
|
||||
return Status::Failure;
|
||||
}
|
||||
};
|
||||
TEST_CASE("Decorator")
|
||||
{
|
||||
int i = 0;
|
||||
SECTION("Inverter")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.inverter().leaf([](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 1);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 2);
|
||||
}
|
||||
|
||||
SECTION("NamedInverter")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.inverter("inverter").leaf([](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 1);
|
||||
}
|
||||
|
||||
SECTION("Succeeder")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.succeeder().leaf([](int& i) { i += 1; return Status::Failure; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 1);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 2);
|
||||
}
|
||||
|
||||
SECTION("NamedSucceeder")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.succeeder("succeeder").leaf([](int& i) { i += 1; return Status::Failure; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 1);
|
||||
}
|
||||
|
||||
SECTION("Repeater")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.repeater(3).leaf([](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 3);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 6);
|
||||
|
||||
i = 0;
|
||||
tree = Builder<int>()
|
||||
.repeater(0).leaf([](int& i) { i += 1; return i > 10 ? Status::Success : Status::Failure; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 11);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 12);
|
||||
}
|
||||
|
||||
SECTION("NamedRepeater")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.repeater("repeater", 3).leaf([](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 3);
|
||||
}
|
||||
|
||||
SECTION("CustomDecorator")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.decorator<CustomDecorator<int>>().leaf([](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 1);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 2);
|
||||
}
|
||||
|
||||
SECTION("NamedCustomDecorator")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.decorator<CustomDecorator<int>>("custom decorator").leaf([](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename Context>
|
||||
class CustomComposite : public Composite<Context>
|
||||
{
|
||||
using Composite<Context>::Composite;
|
||||
public:
|
||||
virtual Status TickImpl(Context& context) const override
|
||||
{
|
||||
for (size_t i = 0; i < this->GetNumChildren(); ++i)
|
||||
{
|
||||
this->TickChild(context, i);
|
||||
}
|
||||
|
||||
return Status::Success;
|
||||
}
|
||||
};
|
||||
TEST_CASE("Composite")
|
||||
{
|
||||
int i = 0;
|
||||
SECTION("Sequence")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.sequence()
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 2);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 4);
|
||||
|
||||
i = 0;
|
||||
tree = Builder<int>()
|
||||
.sequence()
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 3);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 6);
|
||||
}
|
||||
|
||||
SECTION("NamedSequence")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.sequence("sequence")
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 2);
|
||||
}
|
||||
|
||||
SECTION("Empty")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.sequence() // empty sequence is useless but valid
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 0);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 0);
|
||||
}
|
||||
|
||||
SECTION("NamedEmpty")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.sequence("sequence") // empty sequence is useless but valid
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 0);
|
||||
}
|
||||
|
||||
SECTION("Selector")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.selector()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 3);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 6);
|
||||
|
||||
i = 0;
|
||||
tree = Builder<int>()
|
||||
.selector()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 4);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 8);
|
||||
}
|
||||
|
||||
SECTION("NamedSelector")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.selector("selector")
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 3);
|
||||
}
|
||||
|
||||
SECTION("CustomSelector")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.composite<CustomComposite<int>>()
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 3);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 6);
|
||||
}
|
||||
|
||||
SECTION("NamedCustomSelector")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.composite<CustomComposite<int>>("custom composite")
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Subtree")
|
||||
{
|
||||
auto subtree = Builder<int>("tree")
|
||||
.sequence()
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
|
||||
.end();
|
||||
|
||||
auto tree = Builder<int>()
|
||||
.selector()
|
||||
.tree(subtree)
|
||||
.tree(subtree)
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end();
|
||||
|
||||
int i = 0;
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 5);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 10);
|
||||
}
|
||||
|
||||
TEST_CASE("Exceptions")
|
||||
{
|
||||
int i = 0;
|
||||
SECTION("Anonymous")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.inverter().sequence()
|
||||
.selector()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.succeeder().leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end()
|
||||
.selector()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.repeater(2).composite<CustomComposite<int>>()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; throw std::runtime_error("Exception to catch"); return Status::Failure; })
|
||||
.end()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end()
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK_THROWS(tree->Tick(i));
|
||||
CHECK(i == 11);
|
||||
|
||||
try
|
||||
{
|
||||
tree->Tick(i);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
CHECK_THAT(ex.what(), Catch::Matchers::Equals(
|
||||
std::string("In Inverter\n") +
|
||||
"In Sequence while Ticking child 1\n" +
|
||||
"In Selector while Ticking child 2\n" +
|
||||
"In Repeater\n" +
|
||||
"In CustomComposite while Ticking child 3\n" +
|
||||
"Exception to catch")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Named")
|
||||
{
|
||||
auto tree = Builder<int>("tree")
|
||||
.inverter("inverter").sequence("sequence")
|
||||
.selector("selector 0")
|
||||
.leaf("leaf 00", [](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf("leaf 01", [](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf("leaf 02", [](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf("leaf 03", [](int& i) { i += 1; return Status::Failure; })
|
||||
.succeeder("succeeder").leaf("leaf 04", [](int& i) { i += 1; return Status::Success; })
|
||||
.end()
|
||||
.selector("selector 1")
|
||||
.leaf("leaf 10", [](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf("leaf 11", [](int& i) { i += 1; return Status::Failure; })
|
||||
.repeater("repeater", 2).composite<CustomComposite<int>>("custom composite")
|
||||
.leaf("leaf 20", [](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf("leaf 21", [](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf("leaf 22", [](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf("leaf 23", [](int& i) { i += 1; throw std::runtime_error("Exception to catch"); return Status::Failure; })
|
||||
.end()
|
||||
.leaf("leaf 30", [](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf("leaf 31", [](int& i) { i += 1; return Status::Success; })
|
||||
.end()
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
|
||||
try
|
||||
{
|
||||
tree->Tick(i);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
CHECK_THAT(ex.what(), Catch::Matchers::Equals(
|
||||
std::string("In tree \"tree\"\n") +
|
||||
"In \"inverter\" (Inverter)\n" +
|
||||
"In \"sequence\" (Sequence) while Ticking child 1\n" +
|
||||
"In \"selector 1\" (Selector) while Ticking child 2\n" +
|
||||
"In \"repeater\" (Repeater)\n" +
|
||||
"In \"custom composite\" (CustomComposite) while Ticking child 3\n" +
|
||||
"In leaf \"leaf 23\"\n" +
|
||||
"Exception to catch")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Nested")
|
||||
{
|
||||
int i = 0;
|
||||
SECTION("Composite in Composite")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.sequence()
|
||||
.selector()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end()
|
||||
.selector()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.composite<CustomComposite<int>>()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.end()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end()
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 11);
|
||||
}
|
||||
|
||||
SECTION("Composite in decorator")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.inverter().selector()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.inverter().composite<CustomComposite<int>>()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.end()
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 8);
|
||||
}
|
||||
|
||||
SECTION("Decorator in decorator")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.inverter().decorator<CustomDecorator<int>>().repeater(3).leaf([](int& i) { i += 1; return Status::Success; });
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Success);
|
||||
CHECK(i == 3);
|
||||
}
|
||||
|
||||
|
||||
SECTION("Decorator in Composite")
|
||||
{
|
||||
auto tree = Builder<int>()
|
||||
.sequence()
|
||||
.inverter().leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.repeater(3).leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.succeeder().leaf([](int& i) { i += 1; return Status::Failure; })
|
||||
.decorator<CustomDecorator<int>>().leaf([](int& i) { i += 1; return Status::Success; })
|
||||
.end();
|
||||
|
||||
CHECK_FALSE(tree == nullptr);
|
||||
CHECK(tree->Tick(i) == Status::Failure);
|
||||
CHECK(i == 6);
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomContext
|
||||
{
|
||||
void OnNodeStartTick()
|
||||
{
|
||||
num_start_tick += 1;
|
||||
}
|
||||
|
||||
void OnNodeEndTick(const Status s)
|
||||
{
|
||||
num_end_tick += 1;
|
||||
if (s == Status::Success)
|
||||
{
|
||||
num_success_tick += 1;
|
||||
}
|
||||
else if (s == Status::Failure)
|
||||
{
|
||||
num_failure_tick += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void OnNodeTickChild(const size_t index)
|
||||
{
|
||||
num_child_tick += 1;
|
||||
}
|
||||
|
||||
int num_start_tick = 0;
|
||||
int num_end_tick = 0;
|
||||
int num_success_tick = 0;
|
||||
int num_failure_tick = 0;
|
||||
int num_child_tick = 0;
|
||||
};
|
||||
|
||||
TEST_CASE("NodeCallbacks")
|
||||
{
|
||||
auto tree = Builder<CustomContext>()
|
||||
.sequence()
|
||||
.selector()
|
||||
.leaf([](CustomContext& c) { return Status::Failure; })
|
||||
.leaf([](CustomContext& c) { return Status::Failure; })
|
||||
.leaf([](CustomContext& c) { return Status::Success; })
|
||||
.leaf([](CustomContext& c) { return Status::Failure; })
|
||||
.end()
|
||||
.inverter().leaf([](CustomContext& c) { return Status::Failure; })
|
||||
.leaf([](CustomContext& c) { return Status::Success; })
|
||||
.end();
|
||||
|
||||
CustomContext context;
|
||||
const Status s = tree->Tick(context);
|
||||
|
||||
CHECK(s == Status::Success);
|
||||
CHECK(context.num_start_tick == 9);
|
||||
CHECK(context.num_end_tick == 9);
|
||||
CHECK(context.num_success_tick == 6);
|
||||
CHECK(context.num_failure_tick == 3);
|
||||
CHECK(context.num_child_tick == 8);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <botcraft/AI/Blackboard.hpp>
|
||||
|
||||
using namespace Botcraft;
|
||||
|
||||
TEST_CASE("Blackboard Read/Write values")
|
||||
{
|
||||
Blackboard blackboard;
|
||||
|
||||
REQUIRE_NOTHROW(blackboard.Set("hello", std::string("world")));
|
||||
REQUIRE_NOTHROW(blackboard.Get<std::string>("hello"));
|
||||
REQUIRE(blackboard.Get<std::string>("hello") == "world");
|
||||
|
||||
REQUIRE_NOTHROW(blackboard.Set("hello", 3));
|
||||
REQUIRE_THROWS(blackboard.Get<std::string>("hello"));
|
||||
REQUIRE(blackboard.Get<int>("hello") == 3);
|
||||
|
||||
REQUIRE_NOTHROW(blackboard.Copy("hello", "world"));
|
||||
REQUIRE(blackboard.Get<int>("world") == 3);
|
||||
REQUIRE_THROWS(blackboard.Copy("hallo", "world"));
|
||||
|
||||
REQUIRE_NOTHROW(blackboard.Erase("world"));
|
||||
REQUIRE_THROWS(blackboard.Get<int>("world"));
|
||||
|
||||
NotifyOnEndUseRef<int> wrapped_ref = blackboard.GetRef<int>("hello", 42);
|
||||
int& ref = wrapped_ref.ref();
|
||||
REQUIRE(ref == 3);
|
||||
ref = 42;
|
||||
REQUIRE(ref == 42);
|
||||
REQUIRE(blackboard.Get<int>("hello") == 42);
|
||||
|
||||
REQUIRE_NOTHROW(blackboard.Reset());
|
||||
REQUIRE_THROWS(blackboard.Get<int>("hello"));
|
||||
|
||||
REQUIRE_NOTHROW(blackboard.Reset({ { "hello", 42 } }));
|
||||
REQUIRE(blackboard.Get<int>("hello") == 42);
|
||||
}
|
||||
|
||||
class TestBlackboardObserver : public BlackboardObserver
|
||||
{
|
||||
public:
|
||||
void OnReset() override { is_reset = true; }
|
||||
void OnValueChanged(const std::string& key, const std::any& value) override { is_value_changed = true; }
|
||||
void OnValueRemoved(const std::string& key) override { is_value_removed = true; }
|
||||
|
||||
bool is_reset = false;
|
||||
bool is_value_changed = false;
|
||||
bool is_value_removed = false;
|
||||
};
|
||||
|
||||
TEST_CASE("Blackboard notifications")
|
||||
{
|
||||
Blackboard blackboard;
|
||||
TestBlackboardObserver observer;
|
||||
blackboard.Subscribe(&observer);
|
||||
|
||||
REQUIRE_FALSE(observer.is_reset);
|
||||
REQUIRE_FALSE(observer.is_value_changed);
|
||||
REQUIRE_FALSE(observer.is_value_removed);
|
||||
|
||||
blackboard.Set<std::string>("hello", "world");
|
||||
REQUIRE(observer.is_value_changed);
|
||||
|
||||
observer.is_value_changed = false;
|
||||
{
|
||||
const NotifyOnEndUseRef<std::string> wrapped_ref = blackboard.GetRef<std::string>("hello");
|
||||
std::string& ref = wrapped_ref.ref();
|
||||
ref = "world!";
|
||||
}
|
||||
REQUIRE(blackboard.Get<std::string>("hello") == "world!");
|
||||
REQUIRE(observer.is_value_changed);
|
||||
|
||||
blackboard.Erase("hello");
|
||||
REQUIRE(observer.is_value_removed);
|
||||
|
||||
blackboard.Reset();
|
||||
REQUIRE(observer.is_reset);
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include <botcraft/Game/World/Blockstate.hpp>
|
||||
|
||||
using namespace Botcraft;
|
||||
|
||||
TEST_CASE("Testing mining time calculation")
|
||||
{
|
||||
BlockstateProperties blockstate_properties;
|
||||
|
||||
SECTION("air")
|
||||
{
|
||||
blockstate_properties.hardness = -1.0f;
|
||||
blockstate_properties.any_tool_harvest = false;
|
||||
blockstate_properties.best_tools = {
|
||||
|
||||
};
|
||||
Blockstate blockstate(blockstate_properties);
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(-1.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(-1.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(-1.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(-1.0, 0.04));
|
||||
}
|
||||
|
||||
SECTION("water")
|
||||
{
|
||||
blockstate_properties.hardness = 100.0f;
|
||||
blockstate_properties.any_tool_harvest = false;
|
||||
blockstate_properties.water = true;
|
||||
blockstate_properties.best_tools = {
|
||||
|
||||
};
|
||||
Blockstate blockstate(blockstate_properties);
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(-1.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(-1.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(-1.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(-1.0, 0.04));
|
||||
}
|
||||
|
||||
SECTION("obsidian")
|
||||
{
|
||||
blockstate_properties.hardness = 50.0f;
|
||||
blockstate_properties.any_tool_harvest = false;
|
||||
blockstate_properties.best_tools = {
|
||||
BestTool {
|
||||
ToolType::Pickaxe, //tool_type
|
||||
ToolMaterial::Diamond, //tool_material
|
||||
1.0f //multiplier
|
||||
}
|
||||
};
|
||||
Blockstate blockstate(blockstate_properties);
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(250.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(250.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(250.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Gold), Catch::Matchers::WithinAbs(250.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(250.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(125.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Gold), Catch::Matchers::WithinAbs(20.85, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(9.4, 0.04));
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond, 5), Catch::Matchers::WithinAbs(2.25, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond, 0, 2), Catch::Matchers::WithinAbs(6.7, 0.04));
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond, 0, 0, 0, false), Catch::Matchers::WithinAbs(46.9, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond, 0, 0, 0, true, true), Catch::Matchers::WithinAbs(46.9, 0.04));
|
||||
}
|
||||
|
||||
SECTION("ender_chest")
|
||||
{
|
||||
BlockstateProperties blockstate_properties;
|
||||
blockstate_properties.hardness = 22.5f;
|
||||
blockstate_properties.any_tool_harvest = false;
|
||||
blockstate_properties.best_tools = {
|
||||
BestTool {
|
||||
ToolType::Pickaxe, //tool_type
|
||||
ToolMaterial::Wood, //tool_material
|
||||
1.0f //multiplier
|
||||
}
|
||||
};
|
||||
Blockstate blockstate(blockstate_properties);
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(112.5, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(112.5, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(112.5, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Stone), Catch::Matchers::WithinAbs(112.5, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Iron), Catch::Matchers::WithinAbs(112.5, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(16.9, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Gold), Catch::Matchers::WithinAbs(2.85, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(4.25, 0.04));
|
||||
}
|
||||
|
||||
SECTION("chest")
|
||||
{
|
||||
BlockstateProperties blockstate_properties;
|
||||
blockstate_properties.hardness = 2.5f;
|
||||
blockstate_properties.any_tool_harvest = true;
|
||||
blockstate_properties.best_tools = {
|
||||
BestTool {
|
||||
ToolType::Axe, //tool_type
|
||||
ToolMaterial::Wood, //tool_material
|
||||
1.0f //multiplier
|
||||
}
|
||||
};
|
||||
Blockstate blockstate(blockstate_properties);
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(3.75, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(1.9, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Gold), Catch::Matchers::WithinAbs(0.35, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(3.75, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Gold), Catch::Matchers::WithinAbs(3.75, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Stone), Catch::Matchers::WithinAbs(3.75, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(3.75, 0.04));
|
||||
}
|
||||
|
||||
SECTION("dirt")
|
||||
{
|
||||
BlockstateProperties blockstate_properties;
|
||||
blockstate_properties.hardness = 0.5f;
|
||||
blockstate_properties.any_tool_harvest = true;
|
||||
blockstate_properties.best_tools = {
|
||||
BestTool {
|
||||
ToolType::Shovel, //tool_type
|
||||
ToolMaterial::Wood, //tool_material
|
||||
1.0f //multiplier
|
||||
}
|
||||
};
|
||||
Blockstate blockstate(blockstate_properties);
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(0.75, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(0.75, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(0.75, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Gold), Catch::Matchers::WithinAbs(0.75, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(0.75, 0.04));
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Wood), Catch::Matchers::WithinAbs(0.4, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Gold), Catch::Matchers::WithinAbs(0.1, 0.04));
|
||||
#if PROTOCOL_VERSION > 578 /* > 1.15.2 */
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Netherite), Catch::Matchers::WithinAbs(0.1, 0.04));
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("leaves")
|
||||
{
|
||||
BlockstateProperties blockstate_properties;
|
||||
blockstate_properties.hardness = 0.2f;
|
||||
blockstate_properties.any_tool_harvest = true;
|
||||
blockstate_properties.best_tools = {
|
||||
BestTool {
|
||||
ToolType::Shears, //tool_type
|
||||
ToolMaterial::None, //tool_material
|
||||
15.0f //multiplier
|
||||
},
|
||||
BestTool {
|
||||
ToolType::Sword, //tool_type
|
||||
ToolMaterial::None, //tool_material
|
||||
1.5f //multiplier
|
||||
},
|
||||
BestTool {
|
||||
ToolType::Hoe, //tool_type
|
||||
ToolMaterial::Wood, //tool_material
|
||||
1.0f //multiplier
|
||||
},
|
||||
};
|
||||
Blockstate blockstate(blockstate_properties);
|
||||
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(0.3, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(0.0, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Gold), Catch::Matchers::WithinAbs(0.2, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(0.2, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Hoe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(0.15, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Hoe, ToolMaterial::Stone), Catch::Matchers::WithinAbs(0.1, 0.04));
|
||||
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Hoe, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(0.0, 0.04));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#include <catch2/reporters/catch_reporter_event_listener.hpp>
|
||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
||||
|
||||
#include <botcraft/Utilities/Logger.hpp>
|
||||
|
||||
class testRunListener : public Catch::EventListenerBase
|
||||
{
|
||||
public:
|
||||
using Catch::EventListenerBase::EventListenerBase;
|
||||
|
||||
void testRunStarting(Catch::TestRunInfo const&) override
|
||||
{
|
||||
// Set log level to warning before starting the tests
|
||||
Botcraft::Logger::GetInstance().SetLogLevel(Botcraft::LogLevel::Warning);
|
||||
}
|
||||
};
|
||||
|
||||
CATCH_REGISTER_LISTENER(testRunListener)
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <botcraft/Game/AssetsManager.hpp>
|
||||
#include <botcraft/Game/World/World.hpp>
|
||||
#include <botcraft/Game/World/Biome.hpp>
|
||||
|
||||
using namespace Botcraft;
|
||||
|
||||
TEST_CASE("Add/Remove chunks")
|
||||
{
|
||||
World world = World(false);
|
||||
|
||||
#if PROTOCOL_VERSION < 719 /* < 1.16 */
|
||||
const Dimension dimension = Dimension::Overworld;
|
||||
#else
|
||||
const std::string dimension = "minecraft:overworld";
|
||||
#endif
|
||||
|
||||
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
|
||||
world.SetDimensionMinY(dimension, 0);
|
||||
world.SetDimensionHeight(dimension, 256);
|
||||
#endif
|
||||
world.SetCurrentDimension(dimension);
|
||||
|
||||
REQUIRE(world.GetChunks()->size() == 0);
|
||||
|
||||
world.LoadChunk(0, 0, dimension);
|
||||
REQUIRE(world.GetChunks()->size() == 1);
|
||||
|
||||
world.UnloadChunk(0, 0);
|
||||
REQUIRE(world.GetChunks()->size() == 0);
|
||||
|
||||
world.LoadChunk(0, 0, dimension);
|
||||
world.LoadChunk(0, 1, dimension);
|
||||
REQUIRE(world.GetChunks()->size() == 2);
|
||||
|
||||
world.UnloadAllChunks();
|
||||
REQUIRE(world.GetChunks()->size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Set/Get blocks")
|
||||
{
|
||||
World world = World(false);
|
||||
|
||||
#if PROTOCOL_VERSION < 719 /* < 1.16 */
|
||||
const Dimension dimension = Dimension::Overworld;
|
||||
#else
|
||||
const std::string dimension = "minecraft:overworld";
|
||||
#endif
|
||||
|
||||
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
|
||||
world.SetDimensionMinY(dimension, 0);
|
||||
world.SetDimensionHeight(dimension, 256);
|
||||
#endif
|
||||
world.SetCurrentDimension(dimension);
|
||||
#if PROTOCOL_VERSION < 347 /* < 1.13 */
|
||||
const BlockstateId id = { 1,0 };
|
||||
#else
|
||||
const BlockstateId id = 1;
|
||||
#endif
|
||||
|
||||
// Does nothing: chunk not loaded
|
||||
world.SetBlock(Position(0, 0, 0), id);
|
||||
REQUIRE(world.GetBlock(Position(0, 0, 0)) == nullptr);
|
||||
|
||||
world.LoadChunk(0, 0, dimension);
|
||||
world.SetBlock(Position(0, 0, 0), id);
|
||||
REQUIRE(world.GetBlock(Position(0, 0, 0)) != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("Set/Get biomes")
|
||||
{
|
||||
World world = World(false);
|
||||
|
||||
#if PROTOCOL_VERSION < 719 /* < 1.16 */
|
||||
const Dimension dimension = Dimension::Overworld;
|
||||
#else
|
||||
const std::string dimension = "minecraft:overworld";
|
||||
#endif
|
||||
|
||||
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
|
||||
world.SetDimensionMinY(dimension, 0);
|
||||
world.SetDimensionHeight(dimension, 256);
|
||||
#endif
|
||||
world.SetCurrentDimension(dimension);
|
||||
|
||||
// Does nothing: chunk not loaded
|
||||
#if PROTOCOL_VERSION < 552 /* < 1.15 */
|
||||
world.SetBiome(0, 0, 0);
|
||||
#else
|
||||
world.SetBiome(0, 0, 0, 0);
|
||||
#endif
|
||||
REQUIRE(world.GetBiome(Position(0, 0, 0)) == nullptr);
|
||||
|
||||
world.LoadChunk(0, 0, dimension);
|
||||
#if PROTOCOL_VERSION < 552 /* < 1.15 */
|
||||
world.SetBiome(0, 0, 0);
|
||||
#else
|
||||
world.SetBiome(0, 0, 0, 0);
|
||||
#endif
|
||||
REQUIRE(world.GetBiome(Position(0, 0, 0)) != nullptr);
|
||||
REQUIRE(world.GetBiome(Position(0, 0, 0))->GetName() == AssetsManager::getInstance().GetBiome(0)->GetName());
|
||||
}
|
||||
|
||||
#if USE_GUI
|
||||
TEST_CASE("Neighbour chunk update")
|
||||
{
|
||||
World world = World(false);
|
||||
|
||||
#if PROTOCOL_VERSION < 719 /* < 1.16 */
|
||||
const Dimension dimension = Dimension::Overworld;
|
||||
#else
|
||||
const std::string dimension = "minecraft:overworld";
|
||||
#endif
|
||||
|
||||
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
|
||||
world.SetDimensionMinY(dimension, 0);
|
||||
world.SetDimensionHeight(dimension, 256);
|
||||
#endif
|
||||
world.SetCurrentDimension(dimension);
|
||||
|
||||
world.LoadChunk(0, 0, dimension);
|
||||
world.LoadChunk(0, 1, dimension);
|
||||
#if PROTOCOL_VERSION < 347 /* < 1.13 */
|
||||
const BlockstateId id = { 1,0 };
|
||||
#else
|
||||
const BlockstateId id = 1;
|
||||
#endif
|
||||
world.SetBlock(Position(0, 0, CHUNK_WIDTH - 1), id);
|
||||
{
|
||||
auto world_terrain = world.GetChunks();
|
||||
REQUIRE(world_terrain->at({ 0,0 }).GetBlock(Position(0, 0, CHUNK_WIDTH - 1)) != nullptr);
|
||||
REQUIRE(world_terrain->at({ 0,0 }).GetBlock(Position(0, 0, CHUNK_WIDTH - 1))->GetId() == id);
|
||||
REQUIRE(world_terrain->at({ 0,1 }).GetBlock(Position(0, 0, -1)) != nullptr);
|
||||
REQUIRE(world_terrain->at({ 0,1 }).GetBlock(Position(0, 0, -1))->GetId() == id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("Shared world")
|
||||
{
|
||||
std::thread thread1 = std::thread([]() {});
|
||||
std::thread thread2 = std::thread([]() {});
|
||||
const std::thread::id thread_id1 = thread1.get_id();
|
||||
const std::thread::id thread_id2 = thread2.get_id();
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
|
||||
World world = World(true);
|
||||
|
||||
#if PROTOCOL_VERSION < 719 /* < 1.16 */
|
||||
const Dimension dimension = Dimension::Overworld;
|
||||
#else
|
||||
const std::string dimension = "minecraft:overworld";
|
||||
#endif
|
||||
|
||||
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
|
||||
world.SetDimensionMinY(dimension, 0);
|
||||
world.SetDimensionHeight(dimension, 256);
|
||||
#endif
|
||||
world.SetCurrentDimension(dimension);
|
||||
REQUIRE(world.GetChunks()->size() == 0);
|
||||
|
||||
SECTION("Load same chunk")
|
||||
{
|
||||
world.LoadChunk(0, 0, dimension, thread_id1);
|
||||
world.LoadChunk(0, 0, dimension, thread_id2);
|
||||
REQUIRE(world.GetChunks()->size() == 1);
|
||||
|
||||
world.UnloadChunk(0, 0, thread_id1);
|
||||
REQUIRE(world.GetChunks()->size() == 1);
|
||||
world.UnloadChunk(0, 0, thread_id1);
|
||||
REQUIRE(world.GetChunks()->size() == 1);
|
||||
world.UnloadChunk(0, 0, thread_id2);
|
||||
REQUIRE(world.GetChunks()->size() == 0);
|
||||
}
|
||||
|
||||
SECTION("Load different chunks")
|
||||
{
|
||||
world.LoadChunk(0, 0, dimension, thread_id1);
|
||||
world.LoadChunk(0, 1, dimension, thread_id2);
|
||||
REQUIRE(world.GetChunks()->size() == 2);
|
||||
|
||||
world.UnloadAllChunks(thread_id1);
|
||||
REQUIRE(world.GetChunks()->size() == 1);
|
||||
world.UnloadAllChunks(thread_id1);
|
||||
REQUIRE(world.GetChunks()->size() == 1);
|
||||
world.UnloadAllChunks(thread_id2);
|
||||
REQUIRE(world.GetChunks()->size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Set/Get lights")
|
||||
{
|
||||
World world = World(false);
|
||||
|
||||
#if PROTOCOL_VERSION < 719 /* < 1.16 */
|
||||
const Dimension dimension = Dimension::Overworld;
|
||||
#else
|
||||
const std::string dimension = "minecraft:overworld";
|
||||
#endif
|
||||
|
||||
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
|
||||
world.SetDimensionMinY(dimension, 0);
|
||||
world.SetDimensionHeight(dimension, 256);
|
||||
#endif
|
||||
world.SetCurrentDimension(dimension);
|
||||
|
||||
// Does nothing: chunk not loaded
|
||||
world.SetBlockLight(Position(0, 0, 0), 12);
|
||||
CHECK(world.GetBlockLight(Position(0, 0, 0)) == 0);
|
||||
world.SetSkyLight(Position(0, 0, 0), 6);
|
||||
CHECK(world.GetSkyLight(Position(0, 0, 0)) == 0);
|
||||
|
||||
world.LoadChunk(0, 0, dimension);
|
||||
world.SetBlockLight(Position(0, 0, 0), 12);
|
||||
CHECK(world.GetBlockLight(Position(0, 0, 0)) == 12);
|
||||
world.SetBlockLight(Position(1, 0, 0), 6);
|
||||
CHECK(world.GetBlockLight(Position(0, 0, 0)) == 12);
|
||||
CHECK(world.GetBlockLight(Position(1, 0, 0)) == 6);
|
||||
|
||||
world.SetSkyLight(Position(0, 0, 0), 12);
|
||||
CHECK(world.GetSkyLight(Position(0, 0, 0)) == 12);
|
||||
world.SetSkyLight(Position(1, 0, 0), 6);
|
||||
CHECK(world.GetSkyLight(Position(0, 0, 0)) == 12);
|
||||
CHECK(world.GetSkyLight(Position(1, 0, 0)) == 6);
|
||||
}
|
||||
Reference in New Issue
Block a user