1
0

feat: add more rules in BMapInspector

This commit is contained in:
2026-02-04 20:46:04 +08:00
parent 58ee7accff
commit 7b40c64470
10 changed files with 234 additions and 67 deletions

View File

@@ -17,6 +17,7 @@ PRIVATE
Rule/YYCRules.cpp
Rule/ZZQRules.cpp
Rule/BBugRules.cpp
Rule/SOneRules.cpp
)
# Setup headers
target_sources(BMapInspector
@@ -36,6 +37,7 @@ FILES
Rule/YYCRules.hpp
Rule/ZZQRules.hpp
Rule/BBugRules.hpp
Rule/SOneRules.hpp
)
# Setup header infomation
target_include_directories(BMapInspector

View File

@@ -5,6 +5,7 @@
#include "Rule/YYCRules.hpp"
#include "Rule/BBugRules.hpp"
#include "Rule/ZZQRules.hpp"
#include "Rule/SOneRules.hpp"
namespace BMapInspector::Rule {
@@ -20,11 +21,13 @@ namespace BMapInspector::Rule {
Ruleset::Ruleset() : rules() {
// Add rule into list.
//rules.emplace_back(new Gp1Rule());
//rules.emplace_back(new Gp2Rule());
//rules.emplace_back(new GpRule1());
//rules.emplace_back(new GpRule2());
//rules.emplace_back(new Gp3Rule());
//rules.emplace_back(new Chirs1Rule());
rules.emplace_back(new YYCRule1());
rules.emplace_back(new YYCRule2());
rules.emplace_back(new SOneRule1());
// Add more rules...
}

View File

@@ -4,46 +4,50 @@ namespace BMapInspector::Rule {
#pragma region GP1 Rule
Gp1Rule::Gp1Rule() : IRule() {}
GpRule1::GpRule1() : IRule() {}
Gp1Rule::~Gp1Rule() {}
GpRule1::~GpRule1() {}
std::u8string_view Gp1Rule::GetRuleName() const {
std::u8string_view GpRule1::GetRuleName() const {
return u8"GP1";
}
void Gp1Rule::Check(Reporter::Reporter& reporter, Map::Level& level) const {}
void GpRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {}
#pragma endregion
#pragma region GP2 Rule
Gp2Rule::Gp2Rule() : IRule() {}
constexpr char8_t GP2[] = u8"GP2";
Gp2Rule::~Gp2Rule() {}
GpRule2::GpRule2() : IRule() {}
std::u8string_view Gp2Rule::GetRuleName() const {
return u8"GP2";
GpRule2::~GpRule2() {}
std::u8string_view GpRule2::GetRuleName() const {
return GP2;
}
void Gp2Rule::Check(Reporter::Reporter& reporter, Map::Level& level) const {}
void GpRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
}
#pragma endregion
#pragma region GP3 Rule
Gp3Rule::Gp3Rule() : IRule() {}
Gp3Rule::~Gp3Rule() {}
std::u8string_view Gp3Rule::GetRuleName() const {
return u8"GP3";
}
void Gp3Rule::Check(Reporter::Reporter& reporter, Map::Level& level) const {
// TODO: Mesh hash is not implemented.
}
#pragma endregion
//
// Gp3Rule::Gp3Rule() : IRule() {}
//
// Gp3Rule::~Gp3Rule() {}
//
// std::u8string_view Gp3Rule::GetRuleName() const {
// return u8"GP3";
// }
//
// void Gp3Rule::Check(Reporter::Reporter& reporter, Map::Level& level) const {
// // TODO: Mesh hash is not implemented.
// }
//
//#pragma endregion
} // namespace BMapInspector::Rule

View File

@@ -10,11 +10,11 @@ namespace BMapInspector::Rule {
* @details
* The most comprehensive group checker inspired from Ballance Blender Plugin.
*/
class Gp1Rule : public IRule {
class GpRule1 : public IRule {
public:
Gp1Rule();
virtual ~Gp1Rule();
YYCC_DELETE_COPY_MOVE(Gp1Rule)
GpRule1();
virtual ~GpRule1();
YYCC_DELETE_COPY_MOVE(GpRule1)
public:
std::u8string_view GetRuleName() const override;
@@ -24,35 +24,40 @@ namespace BMapInspector::Rule {
/**
* @brief Gamepiaynmo Rule 2
* @details
* This rule make sure that one Ballance element must be grouped into only one sector group.
* Multiple grouping and none grouping will throw error.
* Every Ballance group should not have any groups with same name.
*/
class Gp2Rule : public IRule {
class GpRule2 : public IRule {
public:
Gp2Rule();
virtual ~Gp2Rule();
YYCC_DELETE_COPY_MOVE(Gp2Rule)
GpRule2();
virtual ~GpRule2();
YYCC_DELETE_COPY_MOVE(GpRule2)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief Gamepiaynmo Rule 3
* @details
* This rule make sure that all Ballance element is grouped into correct element group.
* This rule will check the mesh of PH and guess which element it is.
*/
class Gp3Rule : public IRule {
public:
Gp3Rule();
virtual ~Gp3Rule();
YYCC_DELETE_COPY_MOVE(Gp3Rule)
///**
// * @brief Gamepiaynmo Rule 2
// * @details
// * This rule make sure that one Ballance element must be grouped into only one sector group.
// * Multiple grouping and none grouping will throw error.
// */
///**
// * @brief Gamepiaynmo Rule 3
// * @details
// * This rule make sure that all Ballance element is grouped into correct element group.
// * This rule will check the mesh of PH and guess which element it is.
// */
//class Gp3Rule : public IRule {
//public:
// Gp3Rule();
// virtual ~Gp3Rule();
// YYCC_DELETE_COPY_MOVE(Gp3Rule)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
//public:
// std::u8string_view GetRuleName() const override;
// void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
//};
}

View File

@@ -0,0 +1,39 @@
#include "SOneRules.hpp"
#include "Shared.hpp"
namespace L = LibCmo;
namespace C = LibCmo::CK2;
namespace O = LibCmo::CK2::ObjImpls;
namespace BMapInspector::Rule {
#pragma region SOne Rule 1
constexpr char8_t SONE1[] = u8"SONE1";
SOneRule1::SOneRule1() : IRule() {}
SOneRule1::~SOneRule1() {}
std::u8string_view SOneRule1::GetRuleName() const {
return SONE1;
}
void SOneRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
auto* ctx = level.GetCKContext();
auto physicalized_3dobjects = Shared::FetchPhysicalized3dObjects(ctx);
for (auto* physicalized_3dobject : physicalized_3dobjects) {
auto* mesh = physicalized_3dobject->GetCurrentMesh();
if (mesh == nullptr) {
reporter.FormatError(
SONE1,
u8R"(Object "%s" is grouped into physicalization group, but it doesn't have any associated mesh. This will cause itself and following objects can not be physicalized.)",
Shared::RenderObjectName(physicalized_3dobject));
}
}
}
#pragma endregion
} // namespace BMapInspector::Rule

View File

@@ -0,0 +1,23 @@
#pragma once
#include "../Rule.hpp"
namespace BMapInspector::Rule {
/**
* @brief SomeOne_001 Rule 1
* @details
* If there is a physicalized object without any mesh,
* itself and following objects will not be physicalized.
*/
class SOneRule1 : public IRule {
public:
SOneRule1();
virtual ~SOneRule1();
YYCC_DELETE_COPY_MOVE(SOneRule1)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
} // namespace BMapInspector::Rule

View File

@@ -19,6 +19,28 @@ namespace BMapInspector::Rule::Shared {
return static_cast<O::CKGroup*>(ctx->GetObjectByNameAndClass(name, C::CK_CLASSID::CKCID_GROUP, nullptr));
}
static void Iter3dObjectsEx(std::vector<O::CK3dObject*>& container, O::CKGroup* group) {
for (L::CKDWORD obj_idx = 0; obj_idx < group->GetObjectCount(); ++obj_idx) {
auto* group_beobject = group->GetObject(obj_idx);
if (!C::CKIsChildClassOf(group_beobject->GetClassID(), C::CK_CLASSID::CKCID_3DOBJECT)) continue;
auto* group_3dobject = static_cast<O::CK3dObject*>(group_beobject);
container.emplace_back(group_3dobject);
}
}
std::vector<O::CK3dObject*> FetchPhysicalized3dObjects(C::CKContext* ctx) {
std::vector<O::CK3dObject*> rv;
auto* phys_floors = FetchGroup(ctx, GroupNames::PHYS_FLOORS);
if (phys_floors != nullptr) Iter3dObjectsEx(rv, phys_floors);
auto* phys_floorrails = FetchGroup(ctx, GroupNames::PHYS_FLOORRAILS);
if (phys_floorrails != nullptr) Iter3dObjectsEx(rv, phys_floorrails);
auto* phys_floorstopper = FetchGroup(ctx, GroupNames::PHYS_FLOORSTOPPER);
if (phys_floorstopper != nullptr) Iter3dObjectsEx(rv, phys_floorstopper);
return rv;
}
bool CheckTextureFileName(O::CKTexture* tex, L::CKSTRING name) {
// Get file name
auto filename = tex->GetUnderlyingData().GetSlotFileName(0);
@@ -30,14 +52,9 @@ namespace BMapInspector::Rule::Shared {
return C::CKStrEqualI(filename_part.c_str(), name);
}
std::vector<O::CK3dEntity*> Iter3dEntities(O::CKGroup* group) {
std::vector<O::CK3dEntity*> rv;
for (L::CKDWORD obj_idx = 0; obj_idx < group->GetObjectCount(); ++obj_idx) {
auto* group_object = group->GetObject(obj_idx);
if (!C::CKIsChildClassOf(group_object->GetClassID(), C::CK_CLASSID::CKCID_3DENTITY)) continue;
auto* group_3dentity = static_cast<O::CK3dEntity*>(group_object);
rv.emplace_back(group_3dentity);
}
std::vector<O::CK3dObject*> Iter3dObjects(O::CKGroup* group) {
std::vector<O::CK3dObject*> rv;
Iter3dObjectsEx(rv, group);
return rv;
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include <VTAll.hpp>
#include <vector>
#include <array>
namespace BMapInspector::Rule::Shared {
@@ -11,12 +12,19 @@ namespace BMapInspector::Rule::Shared {
#pragma region Constant Values
namespace GroupNames {
// clang-format off
constexpr char8_t PHYS_FLOORS[] = u8"Phys_Floors";
constexpr char8_t PHYS_FLOORRAILS[] = u8"Phys_FloorRails";
}
constexpr char8_t PHYS_FLOORSTOPPER[] = u8"Phys_FloorStopper";
// clang-format on
} // namespace GroupNames
namespace TextureNames {
// clang-format off
constexpr char8_t RAIL_ENVIRONMENT[] = u8"Rail_Environment.bmp";
}
// clang-format on
} // namespace TextureNames
#pragma endregion
@@ -29,6 +37,7 @@ namespace BMapInspector::Rule::Shared {
* @return
*/
O::CKGroup* FetchGroup(C::CKContext* ctx, L::CKSTRING name);
std::vector<O::CK3dObject*> FetchPhysicalized3dObjects(C::CKContext* ctx);
/**
* @brief Check whether given CKTexture has the given file name (case-insensitive).
* @param[in] tex Can not be nullptr.
@@ -42,7 +51,7 @@ namespace BMapInspector::Rule::Shared {
* @param[in] group Can not be nullptr.
* @return All objects is the child class of CK3dEntity.
*/
std::vector<O::CK3dEntity*> Iter3dEntities(O::CKGroup* group);
std::vector<O::CK3dObject*> Iter3dObjects(O::CKGroup* group);
/**
* @brief
* @param[in] mesh Can not be nullptr.

View File

@@ -1,6 +1,8 @@
#include "YYCRules.hpp"
#include "Shared.hpp"
#include <vector>
#include <set>
#include <algorithm>
namespace L = LibCmo;
namespace C = LibCmo::CK2;
@@ -12,7 +14,7 @@ namespace BMapInspector::Rule {
constexpr char8_t YYC1[] = u8"YYC1";
YYCRule1::YYCRule1() {}
YYCRule1::YYCRule1() : IRule() {}
YYCRule1::~YYCRule1() {}
@@ -30,10 +32,10 @@ namespace BMapInspector::Rule {
// Create container holding smooth meshes
std::set<O::CKMesh*> smooth_meshes;
// We iterate all object grouped into it.
auto group_3dentities = Shared::Iter3dEntities(phys_floorrails);
for (auto* group_3dentity : group_3dentities) {
auto group_3dobjects = Shared::Iter3dObjects(phys_floorrails);
for (auto* group_3dobject : group_3dobjects) {
// Then we iterate their current meshes
auto* mesh = group_3dentity->GetCurrentMesh();
auto* mesh = group_3dobject->GetCurrentMesh();
if (mesh == nullptr) continue;
// Iterate all meshes
@@ -47,7 +49,7 @@ namespace BMapInspector::Rule {
reporter.FormatError(
YYC1,
u8R"(Object "%s" is grouped into Phys_FloorRails, but its texture "%s" (referred by mesh %s and material %s) seems not the rail texture. This will cause this object be smooth unexpectly.)",
Shared::RenderObjectName(group_3dentity),
Shared::RenderObjectName(group_3dobject),
Shared::RenderObjectName(texture),
Shared::RenderObjectName(mesh),
Shared::RenderObjectName(mtl));
@@ -81,4 +83,50 @@ namespace BMapInspector::Rule {
#pragma endregion
#pragma region YYC Rule 2
constexpr char8_t YYC2[] = u8"YYC2";
YYCRule2::YYCRule2() : IRule() {}
YYCRule2::~YYCRule2() {}
std::u8string_view YYCRule2::GetRuleName() const {
return YYC2;
}
void YYCRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
auto* ctx = level.GetCKContext();
auto physicalized_3dobjects = Shared::FetchPhysicalized3dObjects(ctx);
// Iterate all physicalized 3dobject
for (auto* physicalized_3dobject : physicalized_3dobjects) {
// Get its mesh
auto* mesh = physicalized_3dobject->GetCurrentMesh();
if (mesh == nullptr) continue;
// Create a bool vector with vertex count and false init value.
std::vector<bool> used_vertex(mesh->GetVertexCount(), false);
// Iterate all face and set their vertex as used.
auto* face_indices = mesh->GetFaceIndices();
for (L::CKDWORD face_idx = 0; face_idx < mesh->GetFaceCount(); ++face_idx) {
used_vertex[face_indices[face_idx * 3]] = true;
used_vertex[face_indices[face_idx * 3 + 1]] = true;
used_vertex[face_indices[face_idx * 3 + 2]] = true;
}
// Check whether there is unused vertex
auto has_unused_vertex = std::any_of(used_vertex.begin(), used_vertex.end(), [](bool v) { return v == false; });
// If there is unused vertex, report error
if (has_unused_vertex) {
reporter.FormatError(
YYC2,
u8R"(Object "%s" is grouped into physicalization groups, and its referred mesh "%s" has isolated vertex. This will cause it can not be physicalized.)",
Shared::RenderObjectName(physicalized_3dobject),
Shared::RenderObjectName(mesh));
}
}
}
#pragma endregion
} // namespace BMapInspector::Rule

View File

@@ -12,7 +12,7 @@ namespace BMapInspector::Rule {
class YYCRule1 : public IRule {
public:
YYCRule1();
~YYCRule1();
virtual ~YYCRule1();
YYCC_DELETE_COPY_MOVE(YYCRule1)
public:
@@ -20,4 +20,21 @@ namespace BMapInspector::Rule {
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief YYC12345 Rule 2
* @details
* The object grouped into physicalization group should not have isolated vertex,
* otherwise it will fail to be physicalized.
*/
class YYCRule2 : public IRule {
public:
YYCRule2();
virtual ~YYCRule2();
YYCC_DELETE_COPY_MOVE(YYCRule2)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
}