#include "attribute_data.h" #include #include #include #include #include #include #include namespace { int failures = 0; #define CHECK(condition, message) \ do { \ if (!(condition)) { \ std::fprintf(stderr, "FAIL: %s (%s:%d)\n", (message), __FILE__, \ __LINE__); \ ++failures; \ } \ } while (0) template void append(std::vector &bytes, const T &value) { const auto *p = reinterpret_cast(&value); bytes.insert(bytes.end(), p, p + sizeof(T)); } void append_name(std::vector &bytes, const char *name) { char fixed[32] = {}; std::strncpy(fixed, name, sizeof(fixed)); bytes.insert(bytes.end(), reinterpret_cast(fixed), reinterpret_cast(fixed) + sizeof(fixed)); } void append_collision(std::vector &bytes, std::uint32_t type, const char *name, const float *position, const float *dimensions, const float *quaternion, std::size_t dimension_count) { append(bytes, type); append_name(bytes, name); for (std::size_t i = 0; i < 3; ++i) append(bytes, position[i]); for (std::size_t i = 0; i < dimension_count; ++i) append(bytes, dimensions[i]); for (std::size_t i = 0; i < 4; ++i) append(bytes, quaternion[i]); } void test_synthetic() { std::vector bytes; const char header[] = "AttributeData"; bytes.insert(bytes.end(), header, header + sizeof(header)); const std::uint32_t collisions = 3; const std::uint32_t heights = 1; append(bytes, collisions); append(bytes, heights); const float p0[] = {10, 20, 30}, d0[] = {40, 50}, q0[] = {0, 0, 0, 1}; append_collision(bytes, 0, "plane", p0, d0, q0, 2); const float p1[] = {-1, 2, -3}, d1[] = {7}, q1[] = {1, 2, 3, 4}; append_collision(bytes, 2, "sphere", p1, d1, q1, 1); const float p2[] = {4, 5, 6}, d2[] = {8, 9, 10}, q2[] = {0, 0.5f, 0, 0.5f}; append_collision(bytes, 5, "obb", p2, d2, q2, 3); append_name(bytes, "height0"); const std::uint32_t vertex_count = 3; append(bytes, vertex_count); const float vertices[] = {0, 0, 0, 100, 0, 0, 0, 100, 0}; for (float value : vertices) append(bytes, value); fmt::AttributeData parsed; std::string error; CHECK(fmt::parse_attribute_data(bytes.data(), bytes.size(), parsed, &error), "synthetic .mdatr parses"); CHECK(parsed.collisions.size() == 3 && parsed.heights.size() == 1, "synthetic counts"); CHECK(parsed.collisions[0].name == "plane" && parsed.collisions[0].dimensions[1] == 50.0f, "plane fields"); CHECK(parsed.collisions[1].type == 2 && parsed.collisions[1].dimensions[0] == 7.0f && parsed.collisions[1].quaternion[0] == 1.0f, "sphere fields"); CHECK(parsed.collisions[2].name == "obb" && parsed.collisions[2].dimensions[2] == 10.0f, "obb fields"); CHECK(parsed.heights[0].name == "height0" && parsed.heights[0].vertices.size() == 3 && parsed.heights[0].vertices[1][0] == 100.0f, "height triangle fields"); auto truncated = bytes; truncated.pop_back(); CHECK(!fmt::parse_attribute_data(truncated.data(), truncated.size(), parsed, &error), "truncated .mdatr rejected"); auto trailing = bytes; trailing.push_back(0); CHECK(!fmt::parse_attribute_data(trailing.data(), trailing.size(), parsed, &error), "trailing .mdatr rejected"); } void test_real_asset() { const char *root = std::getenv("M2_ASSETS"); if (!root || !*root) return; const std::string path = std::string(root) + "/metin2_patch_eu3/ymir work/zone/devilcave/devil_entrance1f.mdatr"; fmt::AttributeData parsed; std::string error; CHECK(fmt::parse_attribute_data_file(path, parsed, &error), "real devil_entrance1f.mdatr parses"); CHECK(parsed.collisions.size() == 9 && parsed.heights.empty(), "real .mdatr collision/height count"); CHECK(parsed.collisions[0].type == 0 && parsed.collisions[0].name == "collision04", "real .mdatr first collision"); } } // namespace int main() { test_synthetic(); test_real_asset(); if (failures == 0) { std::puts("PASS: formats_attribute_data_test"); return 0; } std::fprintf(stderr, "%d check(s) failed\n", failures); return 1; }