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/YYCRules.cpp
Rule/ZZQRules.cpp Rule/ZZQRules.cpp
Rule/BBugRules.cpp Rule/BBugRules.cpp
Rule/SOneRules.cpp
) )
# Setup headers # Setup headers
target_sources(BMapInspector target_sources(BMapInspector
@@ -36,6 +37,7 @@ FILES
Rule/YYCRules.hpp Rule/YYCRules.hpp
Rule/ZZQRules.hpp Rule/ZZQRules.hpp
Rule/BBugRules.hpp Rule/BBugRules.hpp
Rule/SOneRules.hpp
) )
# Setup header infomation # Setup header infomation
target_include_directories(BMapInspector target_include_directories(BMapInspector

View File

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

View File

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

View File

@@ -10,11 +10,11 @@ namespace BMapInspector::Rule {
* @details * @details
* The most comprehensive group checker inspired from Ballance Blender Plugin. * The most comprehensive group checker inspired from Ballance Blender Plugin.
*/ */
class Gp1Rule : public IRule { class GpRule1 : public IRule {
public: public:
Gp1Rule(); GpRule1();
virtual ~Gp1Rule(); virtual ~GpRule1();
YYCC_DELETE_COPY_MOVE(Gp1Rule) YYCC_DELETE_COPY_MOVE(GpRule1)
public: public:
std::u8string_view GetRuleName() const override; std::u8string_view GetRuleName() const override;
@@ -24,35 +24,40 @@ namespace BMapInspector::Rule {
/** /**
* @brief Gamepiaynmo Rule 2 * @brief Gamepiaynmo Rule 2
* @details * @details
* This rule make sure that one Ballance element must be grouped into only one sector group. * Every Ballance group should not have any groups with same name.
* Multiple grouping and none grouping will throw error.
*/ */
class Gp2Rule : public IRule { class GpRule2 : public IRule {
public: public:
Gp2Rule(); GpRule2();
virtual ~Gp2Rule(); virtual ~GpRule2();
YYCC_DELETE_COPY_MOVE(Gp2Rule) YYCC_DELETE_COPY_MOVE(GpRule2)
public: public:
std::u8string_view GetRuleName() const override; std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override; void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
}; };
/** ///**
* @brief Gamepiaynmo Rule 3 // * @brief Gamepiaynmo Rule 2
* @details // * @details
* This rule make sure that all Ballance element is grouped into correct element group. // * This rule make sure that one Ballance element must be grouped into only one sector group.
* This rule will check the mesh of PH and guess which element it is. // * Multiple grouping and none grouping will throw error.
*/ // */
class Gp3Rule : public IRule { ///**
public: // * @brief Gamepiaynmo Rule 3
Gp3Rule(); // * @details
virtual ~Gp3Rule(); // * This rule make sure that all Ballance element is grouped into correct element group.
YYCC_DELETE_COPY_MOVE(Gp3Rule) // * 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: //public:
std::u8string_view GetRuleName() const override; // std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) 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)); 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) { bool CheckTextureFileName(O::CKTexture* tex, L::CKSTRING name) {
// Get file name // Get file name
auto filename = tex->GetUnderlyingData().GetSlotFileName(0); auto filename = tex->GetUnderlyingData().GetSlotFileName(0);
@@ -30,14 +52,9 @@ namespace BMapInspector::Rule::Shared {
return C::CKStrEqualI(filename_part.c_str(), name); return C::CKStrEqualI(filename_part.c_str(), name);
} }
std::vector<O::CK3dEntity*> Iter3dEntities(O::CKGroup* group) { std::vector<O::CK3dObject*> Iter3dObjects(O::CKGroup* group) {
std::vector<O::CK3dEntity*> rv; std::vector<O::CK3dObject*> rv;
for (L::CKDWORD obj_idx = 0; obj_idx < group->GetObjectCount(); ++obj_idx) { Iter3dObjectsEx(rv, group);
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);
}
return rv; return rv;
} }

View File

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

View File

@@ -1,6 +1,8 @@
#include "YYCRules.hpp" #include "YYCRules.hpp"
#include "Shared.hpp" #include "Shared.hpp"
#include <vector>
#include <set> #include <set>
#include <algorithm>
namespace L = LibCmo; namespace L = LibCmo;
namespace C = LibCmo::CK2; namespace C = LibCmo::CK2;
@@ -12,7 +14,7 @@ namespace BMapInspector::Rule {
constexpr char8_t YYC1[] = u8"YYC1"; constexpr char8_t YYC1[] = u8"YYC1";
YYCRule1::YYCRule1() {} YYCRule1::YYCRule1() : IRule() {}
YYCRule1::~YYCRule1() {} YYCRule1::~YYCRule1() {}
@@ -30,10 +32,10 @@ namespace BMapInspector::Rule {
// Create container holding smooth meshes // Create container holding smooth meshes
std::set<O::CKMesh*> smooth_meshes; std::set<O::CKMesh*> smooth_meshes;
// We iterate all object grouped into it. // We iterate all object grouped into it.
auto group_3dentities = Shared::Iter3dEntities(phys_floorrails); auto group_3dobjects = Shared::Iter3dObjects(phys_floorrails);
for (auto* group_3dentity : group_3dentities) { for (auto* group_3dobject : group_3dobjects) {
// Then we iterate their current meshes // Then we iterate their current meshes
auto* mesh = group_3dentity->GetCurrentMesh(); auto* mesh = group_3dobject->GetCurrentMesh();
if (mesh == nullptr) continue; if (mesh == nullptr) continue;
// Iterate all meshes // Iterate all meshes
@@ -47,7 +49,7 @@ namespace BMapInspector::Rule {
reporter.FormatError( reporter.FormatError(
YYC1, 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.)", 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(texture),
Shared::RenderObjectName(mesh), Shared::RenderObjectName(mesh),
Shared::RenderObjectName(mtl)); Shared::RenderObjectName(mtl));
@@ -81,4 +83,50 @@ namespace BMapInspector::Rule {
#pragma endregion #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 } // namespace BMapInspector::Rule

View File

@@ -12,7 +12,7 @@ namespace BMapInspector::Rule {
class YYCRule1 : public IRule { class YYCRule1 : public IRule {
public: public:
YYCRule1(); YYCRule1();
~YYCRule1(); virtual ~YYCRule1();
YYCC_DELETE_COPY_MOVE(YYCRule1) YYCC_DELETE_COPY_MOVE(YYCRule1)
public: public:
@@ -20,4 +20,21 @@ namespace BMapInspector::Rule {
void Check(Reporter::Reporter& reporter, Map::Level& level) const override; 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;
};
} }