1
0

feat: finish one rule in BMapInspector.

- finish one rule in BMapInspector.
- fix CKObjectManager find object by name feature.
This commit is contained in:
2026-02-04 17:03:53 +08:00
parent e6e714f2c9
commit c11220d54b
14 changed files with 268 additions and 19 deletions

View File

@@ -138,6 +138,11 @@ static void CheckRules(BMapInspector::Cli::Args& args, BMapInspector::Map::Level
}
int main(int argc, char* argv[]) {
// Startup CK2 engine.
LibCmo::CK2::CKERROR err = LibCmo::CK2::CKStartUp();
if (err != LibCmo::CK2::CKERROR::CKERR_OK) throw std::runtime_error("fail to initialize CK2 engine.");
auto args = AcceptArgs();
if (args.has_value()) {
PrintSplash();
@@ -148,5 +153,9 @@ int main(int argc, char* argv[]) {
CheckRules(args.value(), level.value());
}
}
// Shutdown CK2 engine.
LibCmo::CK2::CKShutdown();
return 0;
}

View File

@@ -11,6 +11,7 @@ PRIVATE
Map.cpp
Rule.cpp
# Rules
Rule/Shared.cpp
Rule/GpRules.cpp
Rule/ChirsRules.cpp
Rule/YYCRules.cpp
@@ -29,6 +30,7 @@ FILES
Map.hpp
Rule.hpp
# Rules
Rule/Shared.hpp
Rule/GpRules.hpp
Rule/ChirsRules.hpp
Rule/YYCRules.hpp

View File

@@ -92,13 +92,26 @@ namespace BMapInspector::Map {
}
}
YYCC_IMPL_MOVE_CTOR(Level, rhs) : m_Context(rhs.m_Context) {
YYCC_IMPL_MOVE_CTOR(Level, rhs) :
m_Context(rhs.m_Context), m_ObjGroups(std::move(rhs.m_ObjGroups)), m_Obj3dObjects(std::move(rhs.m_Obj3dObjects)),
m_ObjMeshes(std::move(rhs.m_ObjMeshes)), m_ObjMaterials(std::move(rhs.m_ObjMaterials)), m_ObjTextures(std::move(rhs.m_ObjTextures)),
m_ObjTargetLights(std::move(rhs.m_ObjTargetLights))
{
rhs.m_Context = nullptr;
}
YYCC_IMPL_MOVE_OPER(Level, rhs) {
this->m_Context = rhs.m_Context;
this->m_ObjGroups = std::move(rhs.m_ObjGroups);
this->m_Obj3dObjects = std::move(rhs.m_Obj3dObjects);
this->m_ObjMeshes = std::move(rhs.m_ObjMeshes);
this->m_ObjMaterials = std::move(rhs.m_ObjMaterials);
this->m_ObjTextures = std::move(rhs.m_ObjTextures);
this->m_ObjTargetLights = std::move(rhs.m_ObjTargetLights);
rhs.m_Context = nullptr;
return *this;
}

View File

@@ -20,10 +20,11 @@ namespace BMapInspector::Rule {
Ruleset::Ruleset() : rules() {
// Add rule into list.
rules.emplace_back(new Gp1Rule());
rules.emplace_back(new Gp2Rule());
rules.emplace_back(new Gp3Rule());
rules.emplace_back(new Chirs1Rule());
//rules.emplace_back(new Gp1Rule());
//rules.emplace_back(new Gp2Rule());
//rules.emplace_back(new Gp3Rule());
//rules.emplace_back(new Chirs1Rule());
rules.emplace_back(new YYCRule1());
// Add more rules...
}

View File

@@ -19,7 +19,7 @@ namespace BMapInspector::Rule {
public:
virtual std::u8string_view GetRuleName() const = 0;
virtual void Check(Reporter::Reporter& reporter, Map::Level& ctx) const = 0;
virtual void Check(Reporter::Reporter& reporter, Map::Level& level) const = 0;
};
class Ruleset {

View File

@@ -6,7 +6,7 @@ namespace BMapInspector::Rule {
std::u8string_view Chirs1Rule::GetRuleName() const {
return u8"CHIRS1";
}
void Chirs1Rule::Check(Reporter::Reporter& reporter, Map::Level& ctx) const {
void Chirs1Rule::Check(Reporter::Reporter& reporter, Map::Level& level) const {
// Report error if there is some material named Laterne_Verlauf
// but its texture is not pointed to Laterne_Verlauf texture.
@@ -14,6 +14,5 @@ namespace BMapInspector::Rule {
// but its name is not Laterne_Verlauf.
// Report error if there is multiple Laterne_Verlauf material.
reporter.WriteError(this->GetRuleName(), u8"Fork you!");
}
} // namespace BMapInspector::Rule

View File

@@ -19,6 +19,6 @@ namespace BMapInspector::Rule {
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& ctx) const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
}

View File

@@ -12,7 +12,7 @@ namespace BMapInspector::Rule {
return u8"GP1";
}
void Gp1Rule::Check(Reporter::Reporter& reporter, Map::Level& ctx) const {}
void Gp1Rule::Check(Reporter::Reporter& reporter, Map::Level& level) const {}
#pragma endregion
@@ -26,7 +26,7 @@ namespace BMapInspector::Rule {
return u8"GP2";
}
void Gp2Rule::Check(Reporter::Reporter& reporter, Map::Level& ctx) const {}
void Gp2Rule::Check(Reporter::Reporter& reporter, Map::Level& level) const {}
#pragma endregion
@@ -40,7 +40,7 @@ namespace BMapInspector::Rule {
return u8"GP3";
}
void Gp3Rule::Check(Reporter::Reporter& reporter, Map::Level& ctx) const {
void Gp3Rule::Check(Reporter::Reporter& reporter, Map::Level& level) const {
// TODO: Mesh hash is not implemented.
}

View File

@@ -18,7 +18,7 @@ namespace BMapInspector::Rule {
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& ctx) const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
@@ -35,7 +35,7 @@ namespace BMapInspector::Rule {
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& ctx) const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
@@ -52,7 +52,7 @@ namespace BMapInspector::Rule {
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& ctx) const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
}

View File

@@ -0,0 +1,67 @@
#include "Shared.hpp"
#include <yycc.hpp>
#include <yycc/carton/termcolor.hpp>
#include <filesystem>
namespace termcolor = yycc::carton::termcolor;
namespace BMapInspector::Rule::Shared {
#pragma region Check Functions
/**
* @brief
* @param[in] ctx Can not be nullptr.
* @param[in] name Can not be nullptr.
* @return Found pointer to CKGroup, otherwise nullptr.
*/
O::CKGroup* FetchGroup(C::CKContext* ctx, L::CKSTRING name) {
return static_cast<O::CKGroup*>(ctx->GetObjectByNameAndClass(name, C::CK_CLASSID::CKCID_GROUP, nullptr));
}
bool CheckTextureFileName(O::CKTexture* tex, L::CKSTRING name) {
// Get file name
auto filename = tex->GetUnderlyingData().GetSlotFileName(0);
if (filename == nullptr) return false;
// Extract file name part
std::filesystem::path filepath(filename);
auto filename_part = filepath.filename().u8string();
// Return result.
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);
}
return rv;
}
std::vector<O::CKMaterial*> IterMaterial(O::CKMesh* mesh) {
std::vector<O::CKMaterial*> rv;
auto* mtls = mesh->GetMaterialSlots();
for (L::CKDWORD mtl_idx = 0; mtl_idx < mesh->GetMaterialSlotCount(); ++mtl_idx) {
auto* mtl = mtls[mtl_idx];
if (mtl == nullptr) continue;
rv.emplace_back(mtl);
}
return rv;
}
const char8_t* RenderObjectName(O::CKObject* obj) {
static std::u8string ANONYMOUS = termcolor::colored(u8"<anonymous>", termcolor::Color::LightMagenta);
auto name = obj->GetName();
if (name == nullptr) {
return ANONYMOUS.c_str();
} else {
return name;
}
}
#pragma endregion
} // namespace BMapInspector::Rule::Shared

View File

@@ -1,5 +1,62 @@
#pragma once
#include <VTAll.hpp>
#include <vector>
namespace BMapInspector::Rule::Shared {
}
namespace L = LibCmo;
namespace C = LibCmo::CK2;
namespace O = LibCmo::CK2::ObjImpls;
#pragma region Constant Values
namespace GroupNames {
constexpr char8_t PHYS_FLOORRAILS[] = u8"Phys_FloorRails";
}
namespace TextureNames {
constexpr char8_t RAIL_ENVIRONMENT[] = u8"Rail_Environment.bmp";
}
#pragma endregion
#pragma region Check Functions
/**
* @brief
* @param[in] ctx Can not be nullptr.
* @param[in] name Can not be nullptr.
* @return
*/
O::CKGroup* FetchGroup(C::CKContext* ctx, L::CKSTRING name);
/**
* @brief Check whether given CKTexture has the given file name (case-insensitive).
* @param[in] tex Can not be nullptr.
* @param[in] name Can not be nullptr.
* @return True if it is, otherwise false.
*/
bool CheckTextureFileName(O::CKTexture* tex, L::CKSTRING name);
/**
* @brief
* @param[in] group Can not be nullptr.
* @return All objects is the child class of CK3dEntity.
*/
std::vector<O::CK3dEntity*> Iter3dEntities(O::CKGroup* group);
/**
* @brief
* @param[in] mesh Can not be nullptr.
* @return All nullptr reference are removed.
*/
std::vector<O::CKMaterial*> IterMaterial(O::CKMesh* mesh);
/**
* @brief
* @param[in] obj Can not be nullptr.
* @return
*/
const char8_t* RenderObjectName(O::CKObject* obj);
#pragma endregion
} // namespace BMapInspector::Rule::Shared

View File

@@ -0,0 +1,84 @@
#include "YYCRules.hpp"
#include "Shared.hpp"
#include <set>
namespace L = LibCmo;
namespace C = LibCmo::CK2;
namespace O = LibCmo::CK2::ObjImpls;
namespace BMapInspector::Rule {
#pragma region YYC Rule 1
constexpr char8_t YYC1[] = u8"YYC1";
YYCRule1::YYCRule1() {}
YYCRule1::~YYCRule1() {}
std::u8string_view YYCRule1::GetRuleName() const {
return YYC1;
}
void YYCRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
auto* ctx = level.GetCKContext();
// We get "Phys_FloorRails" group first.
auto* phys_floorrails = Shared::FetchGroup(ctx, Shared::GroupNames::PHYS_FLOORRAILS);
if (phys_floorrails == nullptr) return;
// 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) {
// Then we iterate their current meshes
auto* mesh = group_3dentity->GetCurrentMesh();
if (mesh == nullptr) continue;
// Iterate all meshes
auto mtls = Shared::IterMaterial(mesh);
for (auto* mtl : mtls) {
// Check whether all texture referred by this mesh are "Rail_Environment".
auto texture = mtl->GetTexture();
if (texture == nullptr) continue;
if (!Shared::CheckTextureFileName(texture, Shared::TextureNames::RAIL_ENVIRONMENT)) {
// No, this is not rail texture, throw error.
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(texture),
Shared::RenderObjectName(mesh),
Shared::RenderObjectName(mtl));
}
}
// Record this mesh into set.
smooth_meshes.emplace(mesh);
}
// Now we make sure that these smooth mesh is not referred by any other object.
// We iterate all 3d object first
auto all_3dobject = level.Get3dObjects();
for (auto* obj : all_3dobject) {
// Then we get its current mesh
auto* mesh = obj->GetCurrentMesh();
if (mesh == nullptr) continue;
// Check whether its mesh is in smooth mesh,
// and itself is not in "Phys_FloorRails" group
if (!obj->IsInGroup(phys_floorrails) && smooth_meshes.contains(mesh)) {
// Report error.
reporter.FormatError(
YYC1,
u8R"(Object "%s" is not grouped into Phys_FloorRails, but some objects grouped into Phys_FloorRails refer its mesh "%s". This will cause this object be smooth unexpectly.)",
Shared::RenderObjectName(obj),
Shared::RenderObjectName(mesh));
}
}
}
#pragma endregion
} // namespace BMapInspector::Rule

View File

@@ -2,5 +2,22 @@
#include "../Rule.hpp"
namespace BMapInspector::Rule {
/**
* @brief YYC12345 Rule 1
* @details
* The object grouped into "Phys_FloorRails" should only be rails, otherwise their meshes' UV will be smooth.
* Additionally, these smooth UV meshes will also affect those objects refering them.
*/
class YYCRule1 : public IRule {
public:
YYCRule1();
~YYCRule1();
YYCC_DELETE_COPY_MOVE(YYCRule1)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
}

View File

@@ -185,8 +185,8 @@ namespace LibCmo::CK2::MgrImpls {
}
// iterate all sub object and check name
for (const auto& objoff : m_ObjectsListByClass[i]) {
ObjImpls::CKObject* obj = m_ObjectsList[objoff];
for (const auto& objid : m_ObjectsListByClass[i]) {
ObjImpls::CKObject* obj = m_ObjectsList[Id2Offset(objid)];
if (name == nullptr) {
// directly add