1
0

feat: fully refactor BMapInspector rule set for better layout

This commit is contained in:
2026-03-03 17:11:30 +08:00
parent 49a729078c
commit 4c71a20935
35 changed files with 1628 additions and 1428 deletions

View File

@@ -0,0 +1,318 @@
#include "DupCmp.hpp"
#include <yycc.hpp>
#include <yycc/string/op.hpp>
#include <algorithm>
namespace L = LibCmo;
namespace C = LibCmo::CK2;
namespace V = LibCmo::VxMath;
namespace O = LibCmo::CK2::ObjImpls;
namespace strop = yycc::string::op;
namespace std {
#pragma region Primitive CK Hasher
using BMapInspector::Ruleset::Shared::DupCmp::Hasher;
template<>
struct hash<V::VxColor> {
[[nodiscard]] size_t operator()(const V::VxColor& color) const noexcept {
Hasher combiner;
combiner.update(color.r);
combiner.update(color.g);
combiner.update(color.b);
combiner.update(color.a);
return combiner.finish();
}
};
template<>
struct hash<V::VxVector2> {
[[nodiscard]] size_t operator()(const V::VxVector2& vec) const noexcept {
Hasher combiner;
combiner.update(vec.x);
combiner.update(vec.y);
return combiner.finish();
}
};
template<>
struct hash<V::VxVector3> {
[[nodiscard]] size_t operator()(const V::VxVector3& vec) const noexcept {
Hasher combiner;
combiner.update(vec.x);
combiner.update(vec.y);
combiner.update(vec.z);
return combiner.finish();
}
};
#pragma endregion
} // namespace std
namespace BMapInspector::Ruleset::Shared::DupCmp {
#pragma region Hash Combiner
Hasher::Hasher() : seed(FNV_OFFSET_BASIS) {}
Hasher::~Hasher() {}
void Hasher::combine(ValueType h) {
this->seed ^= h;
this->seed *= FNV_PRIME;
}
#pragma endregion
#pragma region CKObject Hash and Equal
size_t CKTextureHash::operator()(const O::CKTexture* tex) const noexcept {
const auto& texdata = tex->GetUnderlyingData();
Hasher combiner;
auto filename = texdata.GetSlotFileName(0);
if (filename == nullptr) {
combiner.update(nullptr);
} else {
auto lower_filename = strop::to_lower(filename);
combiner.update(lower_filename);
}
combiner.update(texdata.GetSaveOptions());
combiner.update(tex->GetVideoFormat());
return combiner.finish();
}
size_t CKMaterialHash::operator()(const O::CKMaterial* mtl) const noexcept {
Hasher combiner;
combiner.update(mtl->GetDiffuse());
combiner.update(mtl->GetAmbient());
combiner.update(mtl->GetSpecular());
combiner.update(mtl->GetEmissive());
combiner.update(mtl->GetSpecularPower());
// TODO:
// Use raw pointer for hash is dangerous.
// But who cares? I simply assume that there is no memory reallocation.
combiner.update(mtl->GetTexture());
combiner.update(mtl->GetTextureBorderColor());
combiner.update(mtl->GetTextureBlendMode());
combiner.update(mtl->GetTextureMinMode());
combiner.update(mtl->GetTextureMagMode());
combiner.update(mtl->GetTextureAddressMode());
combiner.update(mtl->GetSourceBlend());
combiner.update(mtl->GetDestBlend());
combiner.update(mtl->GetFillMode());
combiner.update(mtl->GetShadeMode());
// TODO:
// We also need use these "Enabled" variable to switch on/off
// for some field's hashing according to the Virtools layout.
// But I am lazy now.
// I guess there is the same default values for those fields
// controlled by some disable "Enabled" variable.
combiner.update(mtl->GetAlphaTestEnabled());
combiner.update(mtl->GetAlphaBlendEnabled());
combiner.update(mtl->GetPerspectiveCorrectionEnabled());
combiner.update(mtl->GetZWriteEnabled());
combiner.update(mtl->GetTwoSidedEnabled());
combiner.update(mtl->GetAlphaRef());
combiner.update(mtl->GetAlphaFunc());
combiner.update(mtl->GetZFunc());
return combiner.finish();
}
size_t CKMeshHash::operator()(const O::CKMesh* _mesh) const noexcept {
O::CKMesh* mesh = const_cast<O::CKMesh*>(_mesh);
Hasher combiner;
combiner.update(mesh->GetLitMode());
auto vertex_count = mesh->GetVertexCount();
combiner.update(vertex_count);
combiner.update_array(mesh->GetVertexPositions(), vertex_count);
combiner.update_array(mesh->GetVertexNormals(), vertex_count);
combiner.update_array(mesh->GetVertexUVs(), vertex_count);
// TODO:
// In theory, we need remap face material slot index to underlying material CKID,
// but its too complex. I give up.
auto face_count = mesh->GetFaceCount();
combiner.update(face_count);
combiner.update_array(mesh->GetFaceIndices(), face_count * 3);
combiner.update_array(mesh->GetFaceMaterialSlotIndexs(), face_count);
auto material_slot_count = mesh->GetMaterialSlotCount();
combiner.update(material_slot_count);
// TODO:
// Same dangerous usage of raw pointer.
combiner.update_array(mesh->GetMaterialSlots(), material_slot_count);
return combiner.finish();
}
bool CKTextureEqualTo::operator()(const O::CKTexture* lhs, const O::CKTexture* rhs) const {
// Compare underlying data
const auto& lhs_data = lhs->GetUnderlyingData();
const auto& rhs_data = rhs->GetUnderlyingData();
// Compare filename (case insensitive)
auto lhs_filename = lhs_data.GetSlotFileName(0);
auto rhs_filename = rhs_data.GetSlotFileName(0);
if (!C::CKStrEqualI(lhs_filename, rhs_filename)) return false;
// Compare save options
if (lhs_data.GetSaveOptions() != rhs_data.GetSaveOptions()) return false;
// Compare video format
if (lhs->GetVideoFormat() != rhs->GetVideoFormat()) return false;
return true;
}
bool CKMaterialEqualTo::operator()(const O::CKMaterial* lhs, const O::CKMaterial* rhs) const {
// Compare color properties
if (lhs->GetDiffuse() != rhs->GetDiffuse()) return false;
if (lhs->GetAmbient() != rhs->GetAmbient()) return false;
if (lhs->GetSpecular() != rhs->GetSpecular()) return false;
if (lhs->GetEmissive() != rhs->GetEmissive()) return false;
if (lhs->GetSpecularPower() != rhs->GetSpecularPower()) return false;
// Compare texture properties
if (lhs->GetTexture() != rhs->GetTexture()) return false;
if (lhs->GetTextureBorderColor() != rhs->GetTextureBorderColor()) return false;
// Compare texture modes
if (lhs->GetTextureBlendMode() != rhs->GetTextureBlendMode()) return false;
if (lhs->GetTextureMinMode() != rhs->GetTextureMinMode()) return false;
if (lhs->GetTextureMagMode() != rhs->GetTextureMagMode()) return false;
if (lhs->GetTextureAddressMode() != rhs->GetTextureAddressMode()) return false;
// Compare blend modes
if (lhs->GetSourceBlend() != rhs->GetSourceBlend()) return false;
if (lhs->GetDestBlend() != rhs->GetDestBlend()) return false;
if (lhs->GetFillMode() != rhs->GetFillMode()) return false;
if (lhs->GetShadeMode() != rhs->GetShadeMode()) return false;
// Compare enable flags
if (lhs->GetAlphaTestEnabled() != rhs->GetAlphaTestEnabled()) return false;
if (lhs->GetAlphaBlendEnabled() != rhs->GetAlphaBlendEnabled()) return false;
if (lhs->GetPerspectiveCorrectionEnabled() != rhs->GetPerspectiveCorrectionEnabled()) return false;
if (lhs->GetZWriteEnabled() != rhs->GetZWriteEnabled()) return false;
if (lhs->GetTwoSidedEnabled() != rhs->GetTwoSidedEnabled()) return false;
// Compare alpha and z function properties
if (lhs->GetAlphaRef() != rhs->GetAlphaRef()) return false;
if (lhs->GetAlphaFunc() != rhs->GetAlphaFunc()) return false;
if (lhs->GetZFunc() != rhs->GetZFunc()) return false;
return true;
}
bool CKMeshEqualTo::operator()(const O::CKMesh* _lhs, const O::CKMesh* _rhs) const {
O::CKMesh* lhs = const_cast<O::CKMesh*>(_lhs);
O::CKMesh* rhs = const_cast<O::CKMesh*>(_rhs);
// Compare lit mode
if (lhs->GetLitMode() != rhs->GetLitMode()) return false;
// Compare vertex count
auto vertex_count = lhs->GetVertexCount();
if (vertex_count != rhs->GetVertexCount()) return false;
// Compare vertex data arrays
if (!std::equal(lhs->GetVertexPositions(), lhs->GetVertexPositions() + vertex_count, rhs->GetVertexPositions())) return false;
if (!std::equal(lhs->GetVertexNormals(), lhs->GetVertexNormals() + vertex_count, rhs->GetVertexNormals())) return false;
if (!std::equal(lhs->GetVertexUVs(), lhs->GetVertexUVs() + vertex_count, rhs->GetVertexUVs())) return false;
// Compare face count
auto face_count = lhs->GetFaceCount();
if (face_count != rhs->GetFaceCount()) return false;
// Compare face data arrays
if (!std::equal(lhs->GetFaceIndices(), lhs->GetFaceIndices() + face_count * 3, rhs->GetFaceIndices())) return false;
if (!std::equal(lhs->GetFaceMaterialSlotIndexs(), lhs->GetFaceMaterialSlotIndexs() + face_count, rhs->GetFaceMaterialSlotIndexs()))
return false;
// Compare material slot count
auto material_slot_count = lhs->GetMaterialSlotCount();
if (material_slot_count != rhs->GetMaterialSlotCount()) return false;
// Compare material slots array
if (!std::equal(lhs->GetMaterialSlots(), lhs->GetMaterialSlots() + material_slot_count, rhs->GetMaterialSlots())) return false;
return true;
}
#pragma endregion
#pragma region CKObject Wrapper
CKTextureWrapper::CKTextureWrapper(O::CKTexture* texture) : texture(texture), hasher(), hash(std::nullopt) {}
CKTextureWrapper::~CKTextureWrapper() {}
O::CKTexture* CKTextureWrapper::GetTexture() const { return texture; }
size_t CKTextureWrapper::GetHash() const {
if (!hash.has_value()) hash = hasher(texture);
return hash.value();
}
CKMaterialWrapper::CKMaterialWrapper(O::CKMaterial* material) : material(material), hasher(), hash(std::nullopt) {}
CKMaterialWrapper::~CKMaterialWrapper() {}
O::CKMaterial* CKMaterialWrapper::GetMaterial() const { return material; }
size_t CKMaterialWrapper::GetHash() const {
if (!hash.has_value()) hash = hasher(material);
return hash.value();
}
CKMeshWrapper::CKMeshWrapper(O::CKMesh* mesh) : mesh(mesh), hasher(), hash(std::nullopt) {}
CKMeshWrapper::~CKMeshWrapper() {}
O::CKMesh* CKMeshWrapper::GetMesh() const { return mesh; }
size_t CKMeshWrapper::GetHash() const {
if (!hash.has_value()) hash = hasher(mesh);
return hash.value();
}
#pragma endregion
#pragma region CKObject Wrapper Hash and Equal
size_t CKTextureWrapperHash::operator()(const CKTextureWrapper& tex) const noexcept { return tex.GetHash(); }
size_t CKMaterialWrapperHash::operator()(const CKMaterialWrapper& mtl) const noexcept { return mtl.GetHash(); }
size_t CKMeshWrapperHash::operator()(const CKMeshWrapper& mesh) const noexcept { return mesh.GetHash(); }
bool CKTextureWrapperEqualTo::operator()(const CKTextureWrapper& lhs, const CKTextureWrapper& rhs) const {
if (lhs.GetHash() != rhs.GetHash()) return false;
return equal_to(lhs.GetTexture(), rhs.GetTexture());
}
bool CKMaterialWrapperEqualTo::operator()(const CKMaterialWrapper& lhs, const CKMaterialWrapper& rhs) const {
if (lhs.GetHash() != rhs.GetHash()) return false;
return equal_to(lhs.GetMaterial(), rhs.GetMaterial());
}
bool CKMeshWrapperEqualTo::operator()(const CKMeshWrapper& lhs, const CKMeshWrapper& rhs) const {
if (lhs.GetHash() != rhs.GetHash()) return false;
return equal_to(lhs.GetMesh(), rhs.GetMesh());
}
#pragma endregion
} // namespace BMapInspector::Ruleset::Shared::DupCmp

View File

@@ -0,0 +1,184 @@
#pragma once
#include <VTAll.hpp>
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
#include <yycc/macro/ptr_size_detector.hpp>
#include <utility>
#define BMAPINSP_L LibCmo
#define BMAPINSP_C LibCmo::CK2
#define BMAPINSP_O LibCmo::CK2::ObjImpls
namespace BMapInspector::Ruleset::Shared::DupCmp {
#pragma region Hash Combiner
/**
* @brief FNV-1a Hash Combiner
*/
class Hasher {
public:
using ValueType = size_t;
private:
#if defined(YYCC_PTRSIZE_32)
static constexpr ValueType FNV_OFFSET_BASIS = 2166136261U;
static constexpr ValueType FNV_PRIME = 16777619U;
#else
static constexpr ValueType FNV_OFFSET_BASIS = 14695981039346656037ULL;
static constexpr ValueType FNV_PRIME = 1099511628211ULL;
#endif
public:
Hasher();
~Hasher();
YYCC_DEFAULT_COPY_MOVE(Hasher)
private:
/**
* @brief Update this hash combiner with new hash.
* @param h
*/
void combine(ValueType h);
public:
/**
* @brief Get final produced hash.
* @return
*/
[[nodiscard]] ValueType finish() const noexcept { return this->seed; }
template<typename T>
void update(const T& v) {
std::hash<T> hasher;
combine(hasher(v));
}
template<typename T>
void update_array(const T* addr, size_t cnt) {
std::hash<T> hasher;
for (size_t i = 0; i < cnt; ++i) {
combine(hasher(addr[i]));
}
}
private:
ValueType seed;
};
#pragma endregion
} // namespace BMapInspector::Ruleset::Shared::DupCmp
namespace BMapInspector::Ruleset::Shared::DupCmp {
#pragma region CKObject Hash and Equal
struct CKTextureHash {
[[nodiscard]] size_t operator()(const BMAPINSP_O::CKTexture* tex) const noexcept;
};
struct CKMaterialHash {
[[nodiscard]] size_t operator()(const BMAPINSP_O::CKMaterial* mtl) const noexcept;
};
struct CKMeshHash {
[[nodiscard]] size_t operator()(const BMAPINSP_O::CKMesh* _mesh) const noexcept;
};
struct CKTextureEqualTo {
[[nodiscard]] bool operator()(const BMAPINSP_O::CKTexture* lhs, const BMAPINSP_O::CKTexture* rhs) const;
};
struct CKMaterialEqualTo {
[[nodiscard]] bool operator()(const BMAPINSP_O::CKMaterial* lhs, const BMAPINSP_O::CKMaterial* rhs) const;
};
struct CKMeshEqualTo {
[[nodiscard]] bool operator()(const BMAPINSP_O::CKMesh* _lhs, const BMAPINSP_O::CKMesh* _rhs) const;
};
#pragma endregion
#pragma region CKObject Wrapper
class CKTextureWrapper {
public:
CKTextureWrapper(BMAPINSP_O::CKTexture* texture);
~CKTextureWrapper();
YYCC_DEFAULT_COPY_MOVE(CKTextureWrapper)
public:
BMAPINSP_O::CKTexture* GetTexture() const;
size_t GetHash() const;
private:
BMAPINSP_O::CKTexture* texture;
CKTextureHash hasher;
mutable std::optional<size_t> hash;
};
class CKMaterialWrapper {
public:
CKMaterialWrapper(BMAPINSP_O::CKMaterial* material);
~CKMaterialWrapper();
YYCC_DEFAULT_COPY_MOVE(CKMaterialWrapper)
public:
BMAPINSP_O::CKMaterial* GetMaterial() const;
size_t GetHash() const;
private:
BMAPINSP_O::CKMaterial* material;
CKMaterialHash hasher;
mutable std::optional<size_t> hash;
};
class CKMeshWrapper {
public:
CKMeshWrapper(BMAPINSP_O::CKMesh* mesh);
~CKMeshWrapper();
YYCC_DEFAULT_COPY_MOVE(CKMeshWrapper)
public:
BMAPINSP_O::CKMesh* GetMesh() const;
size_t GetHash() const;
private:
BMAPINSP_O::CKMesh* mesh;
CKMeshHash hasher;
mutable std::optional<size_t> hash;
};
#pragma endregion
#pragma region CKObject Wrapper Hash and Equal
struct CKTextureWrapperHash {
[[nodiscard]] size_t operator()(const CKTextureWrapper& tex) const noexcept;
};
struct CKMaterialWrapperHash {
[[nodiscard]] size_t operator()(const CKMaterialWrapper& mtl) const noexcept;
};
struct CKMeshWrapperHash {
[[nodiscard]] size_t operator()(const CKMeshWrapper& mesh) const noexcept;
};
struct CKTextureWrapperEqualTo {
CKTextureEqualTo equal_to;
[[nodiscard]] bool operator()(const CKTextureWrapper& lhs, const CKTextureWrapper& rhs) const;
};
struct CKMaterialWrapperEqualTo {
CKMaterialEqualTo equal_to;
[[nodiscard]] bool operator()(const CKMaterialWrapper& lhs, const CKMaterialWrapper& rhs) const;
};
struct CKMeshWrapperEqualTo {
CKMeshEqualTo equal_to;
[[nodiscard]] bool operator()(const CKMeshWrapper& lhs, const CKMeshWrapper& rhs) const;
};
#pragma endregion
} // namespace BMapInspector::Ruleset::Shared::DupCmp

View File

@@ -0,0 +1,227 @@
#pragma once
#include <array>
namespace BMapInspector::Ruleset::Shared::Name {
namespace Group {
// clang-format off
constexpr char8_t PS_LEVELSTART[] = u8"PS_Levelstart";
constexpr char8_t PE_LEVELENDE[] = u8"PE_Levelende";
constexpr char8_t PC_CHECKPOINTS[] = u8"PC_Checkpoints";
constexpr char8_t PR_RESETPOINTS[] = u8"PR_Resetpoints";
constexpr char8_t PHYS_FLOORS[] = u8"Phys_Floors";
constexpr char8_t PHYS_FLOORRAILS[] = u8"Phys_FloorRails";
constexpr char8_t PHYS_FLOORSTOPPER[] = u8"Phys_FloorStopper";
constexpr std::array ALL_PH{
u8"P_Extra_Life",
u8"P_Extra_Point",
u8"P_Trafo_Paper",
u8"P_Trafo_Stone",
u8"P_Trafo_Wood",
u8"P_Ball_Paper",
u8"P_Ball_Stone",
u8"P_Ball_Wood",
u8"P_Box",
u8"P_Dome",
u8"P_Modul_01",
u8"P_Modul_03",
u8"P_Modul_08",
u8"P_Modul_17",
u8"P_Modul_18",
u8"P_Modul_19",
u8"P_Modul_25",
u8"P_Modul_26",
u8"P_Modul_29",
u8"P_Modul_30",
u8"P_Modul_34",
u8"P_Modul_37",
u8"P_Modul_41",
u8"PS_Levelstart",
u8"PE_Levelende",
u8"PC_Checkpoints",
u8"PR_Resetpoints",
};
// clang-format on
} // namespace Group
namespace Texture {
// clang-format off
constexpr char8_t RAIL_ENVIRONMENT[] = u8"Rail_Environment.bmp";
constexpr char8_t LATERNE_VERLAUF[] = u8"Laterne_Verlauf.tga";
constexpr std::array OPAQUE_TEXS{
u8"atari.bmp",
u8"Ball_LightningSphere1.bmp",
u8"Ball_LightningSphere2.bmp",
u8"Ball_LightningSphere3.bmp",
u8"Ball_Paper.bmp",
u8"Ball_Stone.bmp",
u8"Ball_Wood.bmp",
u8"Brick.bmp",
u8"Column_beige.bmp",
u8"Column_blue.bmp",
u8"Dome.bmp",
u8"DomeEnvironment.bmp",
u8"ExtraBall.bmp",
u8"ExtraParticle.bmp",
u8"E_Holzbeschlag.bmp",
u8"FloorGlow.bmp",
u8"Floor_Side.bmp",
u8"Floor_Top_Border.bmp",
u8"Floor_Top_Borderless.bmp",
u8"Floor_Top_Checkpoint.bmp",
u8"Floor_Top_Flat.bmp",
u8"Floor_Top_Profil.bmp",
u8"Floor_Top_ProfilFlat.bmp",
u8"Gravitylogo_intro.bmp",
u8"HardShadow.bmp",
u8"Laterne_Glas.bmp",
u8"Logo.bmp",
u8"Metal_stained.bmp",
u8"Misc_Ufo.bmp",
u8"Misc_UFO_Flash.bmp",
u8"Modul03_Floor.bmp",
u8"Modul03_Wall.bmp",
u8"Modul11_13_Wood.bmp",
u8"Modul11_Wood.bmp",
u8"Modul15.bmp",
u8"Modul16.bmp",
u8"Modul18.bmp",
u8"Modul30_d_Seiten.bmp",
u8"Particle_Flames.bmp",
u8"Particle_Smoke.bmp",
u8"PE_Bal_balloons.bmp",
u8"PE_Bal_platform.bmp",
u8"PE_Ufo_env.bmp",
u8"P_Extra_Life_Oil.bmp",
u8"P_Extra_Life_Particle.bmp",
u8"P_Extra_Life_Shadow.bmp",
u8"Rail_Environment.bmp",
u8"sandsack.bmp",
u8"SkyLayer.bmp",
u8"Sky_Vortex.bmp",
u8"Stick_Stripes.bmp",
u8"Target.bmp",
u8"Tower_Roof.bmp",
u8"Trafo_Environment.bmp",
u8"Trafo_FlashField.bmp",
u8"Wood_Metal.bmp",
u8"Wood_MetalStripes.bmp",
u8"Wood_Misc.bmp",
u8"Wood_Nailed.bmp",
u8"Wood_Old.bmp",
u8"Wood_Panel.bmp",
u8"Wood_Plain.bmp",
u8"Wood_Plain2.bmp",
u8"Wood_Raft.bmp",
};
constexpr std::array TRANSPARENT_TEXS{
u8"Button01_deselect.tga",
u8"Button01_select.tga",
u8"Button01_special.tga",
u8"Column_beige_fade.tga",
u8"Cursor.tga",
u8"DomeShadow.tga",
u8"Font_1.tga",
u8"Laterne_Schatten.tga",
u8"Laterne_Verlauf.tga",
u8"Modul18_Gitter.tga",
u8"Pfeil.tga",
u8"Stick_Bottom.tga",
u8"Trafo_Shadow_Big.tga",
u8"Tut_Pfeil01.tga",
u8"Tut_Pfeil_Hoch.tga",
u8"Wolken_intro.tga",
};
constexpr std::array ALL{
// u8"atari.avi",
u8"atari.bmp",
u8"Ball_LightningSphere1.bmp",
u8"Ball_LightningSphere2.bmp",
u8"Ball_LightningSphere3.bmp",
u8"Ball_Paper.bmp",
u8"Ball_Stone.bmp",
u8"Ball_Wood.bmp",
u8"Brick.bmp",
u8"Button01_deselect.tga",
u8"Button01_select.tga",
u8"Button01_special.tga",
u8"Column_beige.bmp",
u8"Column_beige_fade.tga",
u8"Column_blue.bmp",
u8"Cursor.tga",
u8"Dome.bmp",
u8"DomeEnvironment.bmp",
u8"DomeShadow.tga",
u8"ExtraBall.bmp",
u8"ExtraParticle.bmp",
u8"E_Holzbeschlag.bmp",
u8"FloorGlow.bmp",
u8"Floor_Side.bmp",
u8"Floor_Top_Border.bmp",
u8"Floor_Top_Borderless.bmp",
u8"Floor_Top_Checkpoint.bmp",
u8"Floor_Top_Flat.bmp",
u8"Floor_Top_Profil.bmp",
u8"Floor_Top_ProfilFlat.bmp",
u8"Font_1.tga",
u8"Gravitylogo_intro.bmp",
u8"HardShadow.bmp",
u8"Laterne_Glas.bmp",
u8"Laterne_Schatten.tga",
u8"Laterne_Verlauf.tga",
u8"Logo.bmp",
u8"Metal_stained.bmp",
u8"Misc_Ufo.bmp",
u8"Misc_UFO_Flash.bmp",
u8"Modul03_Floor.bmp",
u8"Modul03_Wall.bmp",
u8"Modul11_13_Wood.bmp",
u8"Modul11_Wood.bmp",
u8"Modul15.bmp",
u8"Modul16.bmp",
u8"Modul18.bmp",
u8"Modul18_Gitter.tga",
u8"Modul30_d_Seiten.bmp",
u8"Particle_Flames.bmp",
u8"Particle_Smoke.bmp",
u8"PE_Bal_balloons.bmp",
u8"PE_Bal_platform.bmp",
u8"PE_Ufo_env.bmp",
u8"Pfeil.tga",
u8"P_Extra_Life_Oil.bmp",
u8"P_Extra_Life_Particle.bmp",
u8"P_Extra_Life_Shadow.bmp",
u8"Rail_Environment.bmp",
u8"sandsack.bmp",
u8"SkyLayer.bmp",
u8"Sky_Vortex.bmp",
u8"Stick_Bottom.tga",
u8"Stick_Stripes.bmp",
u8"Target.bmp",
u8"Tower_Roof.bmp",
u8"Trafo_Environment.bmp",
u8"Trafo_FlashField.bmp",
u8"Trafo_Shadow_Big.tga",
u8"Tut_Pfeil01.tga",
u8"Tut_Pfeil_Hoch.tga",
u8"Wolken_intro.tga",
u8"Wood_Metal.bmp",
u8"Wood_MetalStripes.bmp",
u8"Wood_Misc.bmp",
u8"Wood_Nailed.bmp",
u8"Wood_Old.bmp",
u8"Wood_Panel.bmp",
u8"Wood_Plain.bmp",
u8"Wood_Plain2.bmp",
u8"Wood_Raft.bmp",
};
// clang-format on
} // namespace Texture
} // namespace BMapInspector::Ruleset::Shared::Name

View File

@@ -0,0 +1,31 @@
#include "Sector.hpp"
#include <yycc.hpp>
#include <yycc/string/op.hpp>
#include <VTAll.hpp>
namespace strop = yycc::string::op;
namespace L = LibCmo;
namespace BMapInspector::Ruleset::Shared::Sector {
SectorNameBuilder::SectorNameBuilder() {}
SectorNameBuilder::~SectorNameBuilder() {}
SectorName SectorNameBuilder::get_name(L::CKDWORD sector) const {
if (sector < MIN_SECTOR || sector > MAX_SECTOR) {
throw std::logic_error("invalid sector number");
} else {
if (sector < 9) {
return strop::printf(u8"Sector_%02" PRIuCKDWORD, sector);
} else {
return strop::printf(u8"Sector_%" PRIuCKDWORD, sector);
}
}
}
Sector9Names SectorNameBuilder::get_sector9_names() const {
return Sector9Names{.legacy_name = u8"Sector_9", .intuitive_name = u8"Sector_09"};
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <VTAll.hpp>
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
#include <string>
namespace BMapInspector::Ruleset::Shared::Sector {
constexpr LibCmo::CKDWORD MIN_SECTOR = 1;
constexpr LibCmo::CKDWORD MAX_SECTOR = 999;
/**
* @brief The type for sector name.
*/
using SectorName = std::u8string;
struct Sector9Names {
/** The Sector 9 name with "Sector_9" pattern which is accepted by all 999 sector loader */
std::u8string legacy_name;
/** The Sector 9 name with "Sector_09" pattern which is only accepted by new 999 sector loader */
std::u8string intuitive_name;
};
/**
* @brief The class for building Ballance sector group name.
*/
class SectorNameBuilder {
public:
SectorNameBuilder();
~SectorNameBuilder();
YYCC_DEFAULT_COPY_MOVE(SectorNameBuilder)
public:
/**
* @brief Get the sector name.
* @param[in] sector The sector index.
* @return Sector name.
* @remarks
* If you deliver sector index with 9, its return name is "Sector_9" which is accepted by all 999 sector loader.
*/
SectorName get_name(LibCmo::CKDWORD sector) const;
/**
* @brief Get the special sector 9 names.
* @return Special built sector 9 names.
*/
Sector9Names get_sector9_names() const;
};
}

View File

@@ -0,0 +1,122 @@
#include "Utility.hpp"
#include "Name.hpp"
#include <yycc.hpp>
#include <yycc/carton/termcolor.hpp>
#include <filesystem>
#include <stdexcept>
#include <cmath>
namespace strop = yycc::string::op;
namespace termcolor = yycc::carton::termcolor;
namespace L = LibCmo;
namespace C = LibCmo::CK2;
namespace O = LibCmo::CK2::ObjImpls;
namespace BMapInspector::Ruleset::Shared::Utility {
#pragma region Utilities
bool FPEqual(L::CKFLOAT lhs, L::CKFLOAT rhs, L::CKFLOAT tolerance) {
auto diff = lhs - rhs;
auto absolute_diff = std::fabs(diff);
return absolute_diff <= tolerance;
}
#pragma endregion
#pragma region Virtools Stuff
O::CKGroup* FetchGroup(C::CKContext* ctx, L::CKSTRING name) {
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);
}
}
O::CKMaterial* FetchMaterial(C::CKContext* ctx, L::CKSTRING name) {
return static_cast<O::CKMaterial*>(ctx->GetObjectByNameAndClass(name, C::CK_CLASSID::CKCID_MATERIAL, nullptr));
}
std::vector<O::CK3dObject*> FetchPhysicalized3dObjects(C::CKContext* ctx) {
std::vector<O::CK3dObject*> rv;
auto* phys_floors = FetchGroup(ctx, Name::Group::PHYS_FLOORS);
if (phys_floors != nullptr) Iter3dObjectsEx(rv, phys_floors);
auto* phys_floorrails = FetchGroup(ctx, Name::Group::PHYS_FLOORRAILS);
if (phys_floorrails != nullptr) Iter3dObjectsEx(rv, phys_floorrails);
auto* phys_floorstopper = FetchGroup(ctx, Name::Group::PHYS_FLOORSTOPPER);
if (phys_floorstopper != nullptr) Iter3dObjectsEx(rv, phys_floorstopper);
return rv;
}
std::optional<std::u8string> ExtractTextureFileName(O::CKTexture* tex) {
// Get file name
auto filename = tex->GetUnderlyingData().GetSlotFileName(0);
if (filename == nullptr) return std::nullopt;
// Extract file name part
std::filesystem::path filepath(filename);
auto filename_part = filepath.filename().u8string();
return filename_part;
}
bool CheckTextureFileName(O::CKTexture* tex, L::CKSTRING name) {
// Get file name part
auto filename_part = ExtractTextureFileName(tex);
if (!filename_part.has_value()) return false;
// Return result.
return C::CKStrEqualI(filename_part.value().c_str(), name);
}
std::vector<O::CK3dObject*> Iter3dObjects(O::CKGroup* group) {
std::vector<O::CK3dObject*> rv;
Iter3dObjectsEx(rv, group);
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;
}
#pragma endregion
#pragma region Presentation
std::u8string QuoteText(const std::u8string_view& words) {
std::u8string rv;
rv.reserve(words.size() + 2);
rv.push_back('"');
rv.append(words);
rv.push_back('"');
return rv;
}
std::u8string QuoteObjectName(O::CKObject* obj) {
static std::u8string ANONYMOUS = termcolor::colored(u8"<anonymous>", termcolor::Color::LightMagenta);
auto name = obj->GetName();
if (name == nullptr) {
return QuoteText(ANONYMOUS);
} else {
return QuoteText(name);
}
}
#pragma endregion
} // namespace BMapInspector::Ruleset::Shared::Utility

View File

@@ -0,0 +1,118 @@
#pragma once
#include <VTAll.hpp>
#include <yycc.hpp>
#include <yycc/string/op.hpp>
#include <vector>
#include <type_traits>
#define BMAPINSP_L LibCmo
#define BMAPINSP_C LibCmo::CK2
#define BMAPINSP_O LibCmo::CK2::ObjImpls
namespace BMapInspector::Ruleset::Shared::Utility {
#pragma region Utilities
/**
* @brief Check whether given 2 float point values are equal with given tolerance.
* @param[in] lhs The left value to compare.
* @param[in] rhs The right value to compare.
* @param[in] tolerance The tolerance to compare.
* @return True if they are equal with given tolerance, otherwise false.
*/
bool FPEqual(BMAPINSP_L::CKFLOAT lhs, BMAPINSP_L::CKFLOAT rhs, BMAPINSP_L::CKFLOAT tolerance);
#pragma endregion
#pragma region Virtools Stuff
/**
* @brief
* @param[in] ctx Can not be nullptr.
* @param[in] name Can not be nullptr.
* @return Found pointer to CKGroup, otherwise nullptr.
*/
BMAPINSP_O::CKGroup* FetchGroup(BMAPINSP_C::CKContext* ctx, BMAPINSP_L::CKSTRING name);
/**
* @brief
* @param[in] ctx Can not be nullptr.
* @param[in] name Can not be nullptr.
* @return Found pointer to CKMaterial, otherwise nullptr.
*/
BMAPINSP_O::CKMaterial* FetchMaterial(BMAPINSP_C::CKContext* ctx, BMAPINSP_L::CKSTRING name);
std::vector<BMAPINSP_O::CK3dObject*> FetchPhysicalized3dObjects(BMAPINSP_C::CKContext* ctx);
/**
* @brief Extract the file name part of the texture slot associated file path in given CKTexture.
* @param[in] tex The texture for extracting. Can not be nullptr.
* @return Extracted file name part or nothing (there is no associated file path).
*/
std::optional<std::u8string> ExtractTextureFileName(BMAPINSP_O::CKTexture* tex);
/**
* @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(BMAPINSP_O::CKTexture* tex, BMAPINSP_L::CKSTRING name);
/**
* @brief
* @param[in] group Can not be nullptr.
* @return All objects is the child class of CK3dEntity.
*/
std::vector<BMAPINSP_O::CK3dObject*> Iter3dObjects(BMAPINSP_O::CKGroup* group);
/**
* @brief
* @param[in] mesh Can not be nullptr.
* @return All nullptr reference are removed.
*/
std::vector<BMAPINSP_O::CKMaterial*> IterMaterial(BMAPINSP_O::CKMesh* mesh);
#pragma endregion
#pragma region Presentation
/**
* @brief Quote given string.
* @param[in] words The words to quote.
* @return The quoted words.
*/
std::u8string QuoteText(const std::u8string_view& words);
/**
* @brief Quote given CKObject's name.
* @param[in] obj The CKObject for quoting. Can not be nullptr.
* @remarks If there is no name for given CKObject, a colorful name will be quoted and return.
* @return The quoted name.
*/
std::u8string QuoteObjectName(BMAPINSP_O::CKObject* obj);
/**
* @brief Quote given range of CKObject's names.
* @tparam InputIt The type of iterator which iterate non-nullptr pointer to CKObject.
* @param[in] first The iterator to the first element.
* @param[in] last The iterator to the last element.
* @return The quoted names with quote as separator.
*/
template<std::input_iterator InputIt>
requires std::is_pointer_v<std::iter_value_t<InputIt>>
&& std::is_base_of_v<BMAPINSP_O::CKObject, std::remove_pointer_t<std::iter_value_t<InputIt>>>
std::u8string QuoteObjectNames(InputIt first, InputIt last) {
std::u8string cache;
return yycc::string::op::join(
[&cache, &first, &last]() -> std::optional<std::u8string_view> {
if (first == last) return std::nullopt;
// YYC MARK:
// We must use "cache", otherwise "use after free" will occur.
cache = QuoteObjectName(*(first++));
return cache;
},
u8", ");
}
#pragma endregion
} // namespace BMapInspector::Ruleset::Shared::Utility
#undef BMAPINSP_O
#undef BMAPINSP_C
#undef BMAPINSP_L