feat: fully refactor BMapInspector rule set for better layout
This commit is contained in:
64
Ballance/BMapInspector/Ruleset/BBugRules.cpp
Normal file
64
Ballance/BMapInspector/Ruleset/BBugRules.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "BBugRules.hpp"
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region BBug Rule 1
|
||||
|
||||
BBugRule1::BBugRule1() : Rule::IRule() {}
|
||||
|
||||
BBugRule1::~BBugRule1() {}
|
||||
|
||||
std::u8string_view BBugRule1::GetRuleName() const {
|
||||
return u8"BBUG1";
|
||||
}
|
||||
|
||||
void BBugRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
if (!level.GetTargetLights().empty()) {
|
||||
reporter.WriteInfo(u8"Using light in map is not suggested.");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region BBug Rule 2
|
||||
|
||||
BBugRule2::BBugRule2() : Rule::IRule() {}
|
||||
|
||||
BBugRule2::~BBugRule2() {}
|
||||
|
||||
std::u8string_view BBugRule2::GetRuleName() const {
|
||||
return u8"BBUG2";
|
||||
}
|
||||
|
||||
void BBugRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
if (!level.GetTargetCameras().empty()) {
|
||||
reporter.WriteInfo(u8"Using camera in map is not suggested.");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region BBug Rule 3
|
||||
|
||||
BBugRule3::BBugRule3() : Rule::IRule() {}
|
||||
|
||||
BBugRule3::~BBugRule3() {}
|
||||
|
||||
std::u8string_view BBugRule3::GetRuleName() const {
|
||||
return u8"BBUG3";
|
||||
}
|
||||
|
||||
void BBugRule3::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
// TODO:
|
||||
// This function is already presented in Ballance Blender Plugin,
|
||||
// so I don't want write it in there now.
|
||||
// Write this if I have spare time in future.
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
54
Ballance/BMapInspector/Ruleset/BBugRules.hpp
Normal file
54
Ballance/BMapInspector/Ruleset/BBugRules.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief BBug Rule 1
|
||||
* @details
|
||||
* Using light in map is not suggested.
|
||||
*/
|
||||
class BBugRule1 : public Rule::IRule {
|
||||
public:
|
||||
BBugRule1();
|
||||
virtual ~BBugRule1();
|
||||
YYCC_DELETE_COPY_MOVE(BBugRule1)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BBug Rule 2
|
||||
* @details
|
||||
* Using camera in map is not suggested.
|
||||
*/
|
||||
class BBugRule2 : public Rule::IRule {
|
||||
public:
|
||||
BBugRule2();
|
||||
virtual ~BBugRule2();
|
||||
YYCC_DELETE_COPY_MOVE(BBugRule2)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BBug Rule 3
|
||||
* @details
|
||||
* Check whether the parameters of all materials is same with Ballance vanilla settings.
|
||||
*/
|
||||
class BBugRule3 : public Rule::IRule {
|
||||
public:
|
||||
BBugRule3();
|
||||
virtual ~BBugRule3();
|
||||
YYCC_DELETE_COPY_MOVE(BBugRule3)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
}
|
||||
80
Ballance/BMapInspector/Ruleset/ChirsRules.cpp
Normal file
80
Ballance/BMapInspector/Ruleset/ChirsRules.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "ChirsRules.hpp"
|
||||
#include "Shared/Utility.hpp"
|
||||
#include "Shared/Name.hpp"
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region Chirs Rule 1
|
||||
|
||||
// Reference: https://tieba.baidu.com/p/5913556704
|
||||
|
||||
ChirsRule1::ChirsRule1() : Rule::IRule() {}
|
||||
|
||||
ChirsRule1::~ChirsRule1() {}
|
||||
|
||||
std::u8string_view ChirsRule1::GetRuleName() const {
|
||||
return u8"CHIRS1";
|
||||
}
|
||||
|
||||
void ChirsRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
constexpr char8_t MTL_LATERNE_VERLAUF[] = u8"Laterne_Verlauf";
|
||||
auto* ctx = level.GetCKContext();
|
||||
|
||||
// Fetch Laterne_Verlauf first
|
||||
auto* latern = Shared::Utility::FetchMaterial(ctx, MTL_LATERNE_VERLAUF);
|
||||
if (latern == nullptr) return;
|
||||
|
||||
// Report warning if this material's texture is not Laterne_Verlauf.tga
|
||||
auto* latern_tex = latern->GetTexture();
|
||||
if (latern_tex == nullptr) {
|
||||
reporter.FormatWarning(
|
||||
u8"Find a material named %s but it doesn't have associated texture. "
|
||||
u8"It occupies the magic material %s which affect the ray of latern in game. Please confirm this is your intention.",
|
||||
Shared::Utility::QuoteText(MTL_LATERNE_VERLAUF).c_str(),
|
||||
Shared::Utility::QuoteText(MTL_LATERNE_VERLAUF).c_str());
|
||||
} else {
|
||||
if (!Shared::Utility::CheckTextureFileName(latern_tex, Shared::Name::Texture::LATERNE_VERLAUF)) {
|
||||
reporter.FormatWarning(
|
||||
u8"Find a material named %s but its texture is not %s. "
|
||||
u8"It occupies the magic material %s which affect the ray of latern in game. Please confirm this is your intention.",
|
||||
Shared::Utility::QuoteText(MTL_LATERNE_VERLAUF).c_str(),
|
||||
Shared::Utility::QuoteText(Shared::Name::Texture::LATERNE_VERLAUF).c_str(),
|
||||
Shared::Utility::QuoteText(MTL_LATERNE_VERLAUF).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Report warning if there is multiple Laterne_Verlauf material.
|
||||
auto next_latern = ctx->GetObjectByNameAndClass(MTL_LATERNE_VERLAUF, C::CK_CLASSID::CKCID_MATERIAL, latern);
|
||||
if (next_latern != nullptr) {
|
||||
reporter.FormatWarning(u8"There are multiple materials named %s. This will cause the disappearance of some latern's rays.",
|
||||
Shared::Utility::QuoteText(MTL_LATERNE_VERLAUF).c_str());
|
||||
}
|
||||
|
||||
// Report warning if some materials' texture is Laterne_Verlauf,
|
||||
// but its name is not Laterne_Verlauf.
|
||||
for (auto* other_mtl : level.GetMaterials()) {
|
||||
if (C::CKStrEqual(other_mtl->GetName(), MTL_LATERNE_VERLAUF)) continue;
|
||||
|
||||
auto other_mtl_tex = other_mtl->GetTexture();
|
||||
if (other_mtl_tex == nullptr) continue;
|
||||
|
||||
if (Shared::Utility::CheckTextureFileName(other_mtl_tex, Shared::Name::Texture::LATERNE_VERLAUF)) {
|
||||
reporter.FormatWarning(
|
||||
u8"Find material %s referring texture %s, but its name is not %s. "
|
||||
u8"Please confirm the usage of this material. If it is used as %s, please rename it into %s to have correct latern ray.",
|
||||
Shared::Utility::QuoteObjectName(other_mtl).c_str(),
|
||||
Shared::Utility::QuoteText(Shared::Name::Texture::LATERNE_VERLAUF).c_str(),
|
||||
Shared::Utility::QuoteText(MTL_LATERNE_VERLAUF).c_str(),
|
||||
Shared::Utility::QuoteText(MTL_LATERNE_VERLAUF).c_str(),
|
||||
Shared::Utility::QuoteText(MTL_LATERNE_VERLAUF).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
22
Ballance/BMapInspector/Ruleset/ChirsRules.hpp
Normal file
22
Ballance/BMapInspector/Ruleset/ChirsRules.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief chirs241097 Rule 1
|
||||
* @details
|
||||
* This rule will make sure that there is only 1 texture named Laterne_Verlauf in map,
|
||||
* which represent the ray of latern.
|
||||
*/
|
||||
class ChirsRule1 : public Rule::IRule {
|
||||
public:
|
||||
ChirsRule1();
|
||||
virtual ~ChirsRule1();
|
||||
YYCC_DELETE_COPY_MOVE(ChirsRule1)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
}
|
||||
138
Ballance/BMapInspector/Ruleset/GpRules.cpp
Normal file
138
Ballance/BMapInspector/Ruleset/GpRules.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "GpRules.hpp"
|
||||
#include "Shared/Utility.hpp"
|
||||
#include "Shared/Sector.hpp"
|
||||
#include <set>
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region GP1 Rule
|
||||
|
||||
// Reference: https://tieba.baidu.com/p/3182981807
|
||||
|
||||
GpRule1::GpRule1() : Rule::IRule() {}
|
||||
|
||||
GpRule1::~GpRule1() {}
|
||||
|
||||
std::u8string_view GpRule1::GetRuleName() const {
|
||||
return u8"GP1";
|
||||
}
|
||||
|
||||
void GpRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
// TODO:
|
||||
// Finish this rule.
|
||||
// It is so complex that I don't want to implement it now.
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region GP2 Rule
|
||||
|
||||
GpRule2::GpRule2() : Rule::IRule() {}
|
||||
|
||||
GpRule2::~GpRule2() {}
|
||||
|
||||
std::u8string_view GpRule2::GetRuleName() const {
|
||||
return u8"GP2";
|
||||
}
|
||||
|
||||
void GpRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
Shared::Sector::SectorNameBuilder builder;
|
||||
|
||||
// We need collect all group names first,
|
||||
// becuase in following code we need frequent visit them
|
||||
std::set<std::u8string> group_names;
|
||||
for (auto* group : level.GetGroups()) {
|
||||
auto group_name = group->GetName();
|
||||
if (group_name != nullptr) {
|
||||
group_names.emplace(std::u8string(group_name));
|
||||
}
|
||||
}
|
||||
|
||||
// Check the sector count of this game.
|
||||
L::CKDWORD sector_count;
|
||||
for (sector_count = Shared::Sector::MIN_SECTOR; sector_count <= Shared::Sector::MAX_SECTOR; ++sector_count) {
|
||||
// Build name first with special treat for sector 9
|
||||
if (sector_count != 9) {
|
||||
auto sector_name = builder.get_name(sector_count);
|
||||
if (!group_names.contains(sector_name)) {
|
||||
if (sector_count == Shared::Sector::MIN_SECTOR) {
|
||||
reporter.WriteError(u8"Can not find any reasonable sector group in your map.");
|
||||
return;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto sector_names = builder.get_sector9_names();
|
||||
bool has_legacy_sector = group_names.contains(sector_names.legacy_name);
|
||||
bool has_intuitive_sector = group_names.contains(sector_names.intuitive_name);
|
||||
if (!has_legacy_sector && !has_intuitive_sector) {
|
||||
break;
|
||||
}
|
||||
if (has_legacy_sector && has_intuitive_sector) {
|
||||
reporter.FormatError(u8"Found %s and %s at the same map. This is not allowed.",
|
||||
Shared::Utility::QuoteText(sector_names.legacy_name).c_str(),
|
||||
Shared::Utility::QuoteText(sector_names.intuitive_name).c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now sector_count is the first sector which can not find,
|
||||
// so we need minus one on it.
|
||||
--sector_count;
|
||||
|
||||
// Report sector count info.
|
||||
reporter.FormatInfo(u8"Your map has %" PRIuCKDWORD " sector(s).", sector_count);
|
||||
// Report special warning for map which only contain 1 sector.
|
||||
if (sector_count == 1) {
|
||||
reporter.WriteWarning(u8"Your map only have one sector. "
|
||||
u8"This is okey but not suggested because it will cause mosaic issue on the flames of checkpoint. "
|
||||
u8"Consider adding another sector to resolve this issue.");
|
||||
}
|
||||
// Report warning for sector greater than 8.
|
||||
if (sector_count > 8) {
|
||||
reporter.WriteWarning(u8"You are creating a map with more than 8 sectors. "
|
||||
u8"This will cause vanilla Ballance freezed when loading it. "
|
||||
u8"Please make sure that all players of your map have properly set 999 sector loader up.");
|
||||
}
|
||||
// If there is sector 9, check its kind and report wanring if it is intuitive kind.
|
||||
if (sector_count > 8) {
|
||||
auto sector_names = builder.get_sector9_names();
|
||||
if (group_names.contains(sector_names.intuitive_name)) {
|
||||
reporter.FormatWarning(u8"You are using intuitive sector name, %s, for sector 9. "
|
||||
u8"This is only accepted by new 999 sector loader.",
|
||||
Shared::Utility::QuoteText(sector_names.intuitive_name).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// We continue check following sectors to make sure that all sector is successive.
|
||||
for (L::CKDWORD i = sector_count + 1; i <= Shared::Sector::MAX_SECTOR; ++i) {
|
||||
if (i != 9) {
|
||||
auto sector_name = builder.get_name(i);
|
||||
if (group_names.contains(sector_name)) {
|
||||
reporter.FormatError(u8"Found group %s unexpected. "
|
||||
u8"Please check whether sector groups are successive in your map.",
|
||||
Shared::Utility::QuoteText(sector_name).c_str());
|
||||
}
|
||||
} else {
|
||||
auto sector_names = builder.get_sector9_names();
|
||||
bool has_legacy_sector = group_names.contains(sector_names.legacy_name);
|
||||
bool has_intuitive_sector = group_names.contains(sector_names.intuitive_name);
|
||||
if (has_legacy_sector || has_intuitive_sector) {
|
||||
reporter.FormatError(u8"Found group %s or %s unexpected. "
|
||||
u8"Please check whether sector groups are successive in your map.",
|
||||
Shared::Utility::QuoteText(sector_names.legacy_name).c_str(),
|
||||
Shared::Utility::QuoteText(sector_names.intuitive_name).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
44
Ballance/BMapInspector/Ruleset/GpRules.hpp
Normal file
44
Ballance/BMapInspector/Ruleset/GpRules.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief Gamepiaynmo Rule 1
|
||||
* @details
|
||||
* The most comprehensive group checker inspired from Ballance Blender Plugin.
|
||||
*/
|
||||
class GpRule1 : public Rule::IRule {
|
||||
public:
|
||||
GpRule1();
|
||||
virtual ~GpRule1();
|
||||
YYCC_DELETE_COPY_MOVE(GpRule1)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Gamepiaynmo Rule 2
|
||||
* @details
|
||||
* This rule will:
|
||||
* \li Show how many sector located in given map.
|
||||
* \li Check whether there is sector group.
|
||||
* \li Check whether use intuitive sector name for sector 9.
|
||||
* \li Warn for sector count is equal to 1. It will cause mosaic issue on the flames of checkpoint.
|
||||
* \li Warn for sector count greater than 8. It will cause vanilla game freezed without 999 sector loader.
|
||||
* \li Check whether sector group is successive.
|
||||
*/
|
||||
class GpRule2 : public Rule::IRule {
|
||||
public:
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
116
Ballance/BMapInspector/Ruleset/LXRules.cpp
Normal file
116
Ballance/BMapInspector/Ruleset/LXRules.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "LXRules.hpp"
|
||||
#include "Shared/Utility.hpp"
|
||||
#include "Shared/Name.hpp"
|
||||
#include <set>
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region LX Rule 1
|
||||
|
||||
LXRule1::LXRule1() : Rule::IRule() {}
|
||||
|
||||
LXRule1::~LXRule1() {}
|
||||
|
||||
std::u8string_view LXRule1::GetRuleName() const {
|
||||
return u8"LX1";
|
||||
}
|
||||
|
||||
void LXRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
|
||||
// First we fetch all Ballance element and push them into set.
|
||||
std::set<O::CK3dObject*> elements;
|
||||
for (auto* group_name : Shared::Name::Group::ALL_PH) {
|
||||
auto* group = Shared::Utility::FetchGroup(ctx, group_name);
|
||||
if (group == nullptr) continue;
|
||||
auto group_objects = Shared::Utility::Iter3dObjects(group);
|
||||
for (auto* group_object : group_objects) {
|
||||
elements.emplace(group_object);
|
||||
}
|
||||
}
|
||||
|
||||
// Then we analyse their mesh, material, texture and fill corresponding set.
|
||||
std::set<O::CKMesh*> element_meshes;
|
||||
std::set<O::CKMaterial*> element_materials;
|
||||
std::set<O::CKTexture*> element_textures;
|
||||
for (auto* element_object : elements) {
|
||||
auto* mesh = element_object->GetCurrentMesh();
|
||||
if (mesh == nullptr) continue;
|
||||
|
||||
// Add into mesh set
|
||||
auto mesh_insert_rv = element_meshes.emplace(mesh);
|
||||
|
||||
// Only process it if we have inserted it
|
||||
// because we do not want to duplicatedly process it.
|
||||
if (mesh_insert_rv.second) {
|
||||
// Iterate all meshes
|
||||
auto mtls = Shared::Utility::IterMaterial(mesh);
|
||||
for (auto* mtl : mtls) {
|
||||
// Add into material set
|
||||
auto mtl_insert_rv = element_materials.emplace(mtl);
|
||||
|
||||
// Also only process it if we have inserted it
|
||||
if (mtl_insert_rv.second) {
|
||||
// Fetch texture
|
||||
auto texture = mtl->GetTexture();
|
||||
if (texture == nullptr) continue;
|
||||
// And insert it
|
||||
element_textures.emplace(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, check any other object whether use these data.
|
||||
for (auto* other_object : level.Get3dObjects()) {
|
||||
// If it is element, skip it.
|
||||
if (elements.contains(other_object)) continue;
|
||||
|
||||
// Get mesh
|
||||
auto* mesh = other_object->GetCurrentMesh();
|
||||
if (mesh == nullptr) continue;
|
||||
// And check mesh
|
||||
if (element_meshes.contains(mesh)) {
|
||||
reporter.FormatError(u8"Object %s used mesh %s is already used by a Ballance element. "
|
||||
u8"This will cause this object can not be rendered correctly in level.",
|
||||
Shared::Utility::QuoteObjectName(other_object).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mesh).c_str());
|
||||
} else {
|
||||
// If not, check material.
|
||||
// Iterate all meshes
|
||||
auto mtls = Shared::Utility::IterMaterial(mesh);
|
||||
for (auto* mtl : mtls) {
|
||||
if (element_materials.contains(mtl)) {
|
||||
reporter.FormatError(u8"Object %s used material %s (referred by mesh %s) is already used by a Ballance element. "
|
||||
u8"This will cause this object can not be rendered correctly in level.",
|
||||
Shared::Utility::QuoteObjectName(other_object).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mtl).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mesh).c_str());
|
||||
} else {
|
||||
// Still not, check texture.
|
||||
// Fetch texture
|
||||
auto texture = mtl->GetTexture();
|
||||
if (texture == nullptr) continue;
|
||||
// And check it
|
||||
if (element_textures.contains(texture)) {
|
||||
reporter.FormatError(
|
||||
u8"Object %s used texture %s (referred by mesh %s and material %s) is already used by a Ballance element. "
|
||||
u8"This will cause this object can not be rendered correctly in level.",
|
||||
Shared::Utility::QuoteObjectName(other_object).c_str(),
|
||||
Shared::Utility::QuoteObjectName(texture).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mesh).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mtl).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
22
Ballance/BMapInspector/Ruleset/LXRules.hpp
Normal file
22
Ballance/BMapInspector/Ruleset/LXRules.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief LengXi Rule 1
|
||||
* @details
|
||||
* All meshes, materials and textures used by Ballance elements should not be used by any other objects.
|
||||
*/
|
||||
class LXRule1 : public Rule::IRule {
|
||||
public:
|
||||
LXRule1();
|
||||
virtual ~LXRule1();
|
||||
YYCC_DELETE_COPY_MOVE(LXRule1)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
}
|
||||
36
Ballance/BMapInspector/Ruleset/SOneRules.cpp
Normal file
36
Ballance/BMapInspector/Ruleset/SOneRules.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "SOneRules.hpp"
|
||||
#include "Shared/Utility.hpp"
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region SOne Rule 1
|
||||
|
||||
SOneRule1::SOneRule1() : Rule::IRule() {}
|
||||
|
||||
SOneRule1::~SOneRule1() {}
|
||||
|
||||
std::u8string_view SOneRule1::GetRuleName() const {
|
||||
return u8"SONE1";
|
||||
}
|
||||
|
||||
void SOneRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
auto physicalized_3dobjects = Shared::Utility::FetchPhysicalized3dObjects(ctx);
|
||||
|
||||
for (auto* physicalized_3dobject : physicalized_3dobjects) {
|
||||
auto* mesh = physicalized_3dobject->GetCurrentMesh();
|
||||
if (mesh == nullptr) {
|
||||
reporter.FormatError(u8"Object %s is grouped into physicalization group, but it doesn't have any associated mesh. "
|
||||
u8"This will cause itself and following objects can not be physicalized.",
|
||||
Shared::Utility::QuoteObjectName(physicalized_3dobject).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
23
Ballance/BMapInspector/Ruleset/SOneRules.hpp
Normal file
23
Ballance/BMapInspector/Ruleset/SOneRules.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @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 Rule::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::Ruleset
|
||||
58
Ballance/BMapInspector/Ruleset/SSBRules.cpp
Normal file
58
Ballance/BMapInspector/Ruleset/SSBRules.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "SSBRules.hpp"
|
||||
#include "Shared/Utility.hpp"
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace V = LibCmo::VxMath;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region SSB Rule 1
|
||||
|
||||
constexpr L::CKFLOAT TOLERANCE = 0.001f;
|
||||
|
||||
SSBRule1::SSBRule1() : Rule::IRule() {}
|
||||
|
||||
SSBRule1::~SSBRule1() {}
|
||||
|
||||
std::u8string_view SSBRule1::GetRuleName() const {
|
||||
return u8"SSB1";
|
||||
}
|
||||
|
||||
void SSBRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
auto physicalized_3dobjects = Shared::Utility::FetchPhysicalized3dObjects(ctx);
|
||||
|
||||
// Iterate all physicalized 3dobject
|
||||
for (auto* physicalized_3dobject : physicalized_3dobjects) {
|
||||
// Get its world matrix
|
||||
const auto& matrix = physicalized_3dobject->GetWorldMatrix();
|
||||
|
||||
// YYC MARK:
|
||||
// Following method is the bad way to check scale factor,
|
||||
// because it rely on some premise.
|
||||
// But it is simple, especially we do not have fully implement VxMatrix,
|
||||
// or have any linear algebra library.
|
||||
|
||||
// Extract 3 columns
|
||||
V::VxVector3 col1(matrix[0][0], matrix[1][0], matrix[2][0]);
|
||||
V::VxVector3 col2(matrix[0][1], matrix[1][1], matrix[2][1]);
|
||||
V::VxVector3 col3(matrix[0][2], matrix[1][2], matrix[2][2]);
|
||||
// Compute their length, then check their value with tolerance.
|
||||
bool has_scale = false;
|
||||
if (!Shared::Utility::FPEqual(col1.Length(), 1.0f, TOLERANCE)) has_scale = true;
|
||||
if (!Shared::Utility::FPEqual(col2.Length(), 1.0f, TOLERANCE)) has_scale = true;
|
||||
if (!Shared::Utility::FPEqual(col3.Length(), 1.0f, TOLERANCE)) has_scale = true;
|
||||
// If it has scale factor, report error
|
||||
if (has_scale) {
|
||||
reporter.FormatError(u8"Object %s grouped into physicalization groups has scale factor. "
|
||||
u8"This will cause its collision shape is different with its render shape.",
|
||||
Shared::Utility::QuoteObjectName(physicalized_3dobject).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
22
Ballance/BMapInspector/Ruleset/SSBRules.hpp
Normal file
22
Ballance/BMapInspector/Ruleset/SSBRules.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief speedystoneball Rule 1
|
||||
* @details
|
||||
* Physicalized object should not have scale factor, especially negative scale factor (mirror).
|
||||
*/
|
||||
class SSBRule1 : public Rule::IRule {
|
||||
public:
|
||||
SSBRule1();
|
||||
virtual ~SSBRule1();
|
||||
YYCC_DELETE_COPY_MOVE(SSBRule1)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
318
Ballance/BMapInspector/Ruleset/Shared/DupCmp.cpp
Normal file
318
Ballance/BMapInspector/Ruleset/Shared/DupCmp.cpp
Normal 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
|
||||
184
Ballance/BMapInspector/Ruleset/Shared/DupCmp.hpp
Normal file
184
Ballance/BMapInspector/Ruleset/Shared/DupCmp.hpp
Normal 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
|
||||
0
Ballance/BMapInspector/Ruleset/Shared/Name.cpp
Normal file
0
Ballance/BMapInspector/Ruleset/Shared/Name.cpp
Normal file
227
Ballance/BMapInspector/Ruleset/Shared/Name.hpp
Normal file
227
Ballance/BMapInspector/Ruleset/Shared/Name.hpp
Normal 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
|
||||
31
Ballance/BMapInspector/Ruleset/Shared/Sector.cpp
Normal file
31
Ballance/BMapInspector/Ruleset/Shared/Sector.cpp
Normal 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"};
|
||||
}
|
||||
|
||||
}
|
||||
49
Ballance/BMapInspector/Ruleset/Shared/Sector.hpp
Normal file
49
Ballance/BMapInspector/Ruleset/Shared/Sector.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
122
Ballance/BMapInspector/Ruleset/Shared/Utility.cpp
Normal file
122
Ballance/BMapInspector/Ruleset/Shared/Utility.cpp
Normal 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
|
||||
118
Ballance/BMapInspector/Ruleset/Shared/Utility.hpp
Normal file
118
Ballance/BMapInspector/Ruleset/Shared/Utility.hpp
Normal 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
|
||||
298
Ballance/BMapInspector/Ruleset/YYCRules.cpp
Normal file
298
Ballance/BMapInspector/Ruleset/YYCRules.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
#include "YYCRules.hpp"
|
||||
#include "Shared/Utility.hpp"
|
||||
#include "Shared/Name.hpp"
|
||||
#include "Shared/DupCmp.hpp"
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/op.hpp>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace V = LibCmo::VxMath;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
namespace strop = yycc::string::op;
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region YYC Rule 1
|
||||
|
||||
YYCRule1::YYCRule1() : Rule::IRule() {}
|
||||
|
||||
YYCRule1::~YYCRule1() {}
|
||||
|
||||
std::u8string_view YYCRule1::GetRuleName() const {
|
||||
return u8"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::Utility::FetchGroup(ctx, Shared::Name::Group::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_3dobjects = Shared::Utility::Iter3dObjects(phys_floorrails);
|
||||
for (auto* group_3dobject : group_3dobjects) {
|
||||
// Then we iterate their current meshes
|
||||
auto* mesh = group_3dobject->GetCurrentMesh();
|
||||
if (mesh == nullptr) continue;
|
||||
|
||||
// Iterate all meshes
|
||||
auto mtls = Shared::Utility::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::Utility::CheckTextureFileName(texture, Shared::Name::Texture::RAIL_ENVIRONMENT)) {
|
||||
// No, this is not rail texture, throw error.
|
||||
reporter.FormatError(
|
||||
u8"Object %s is grouped into %s, but its texture %s (referred by mesh %s and material %s) seems not the rail texture. "
|
||||
u8"This will cause some parts of this object be smooth unexpectly.",
|
||||
Shared::Utility::QuoteObjectName(group_3dobject).c_str(),
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PHYS_FLOORRAILS).c_str(),
|
||||
Shared::Utility::QuoteObjectName(texture).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mesh).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mtl).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// 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(u8"Object %s is not grouped into %s, but some objects grouped into %s refer its mesh %s. "
|
||||
u8"This will cause this object be smooth unexpectly.",
|
||||
Shared::Utility::QuoteObjectName(obj).c_str(),
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PHYS_FLOORRAILS).c_str(),
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PHYS_FLOORRAILS).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mesh).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region YYC Rule 2
|
||||
|
||||
YYCRule2::YYCRule2() : Rule::IRule() {}
|
||||
|
||||
YYCRule2::~YYCRule2() {}
|
||||
|
||||
std::u8string_view YYCRule2::GetRuleName() const {
|
||||
return u8"YYC2";
|
||||
}
|
||||
|
||||
void YYCRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
auto physicalized_3dobjects = Shared::Utility::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(u8"Object %s is grouped into physicalization groups, and its referred mesh %s has isolated vertex. "
|
||||
u8"This will cause it can not be physicalized.",
|
||||
Shared::Utility::QuoteObjectName(physicalized_3dobject).c_str(),
|
||||
Shared::Utility::QuoteObjectName(mesh).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region YYC Rule 3
|
||||
|
||||
YYCRule3::YYCRule3() : Rule::IRule() {}
|
||||
|
||||
YYCRule3::~YYCRule3() {}
|
||||
|
||||
std::u8string_view YYCRule3::GetRuleName() const {
|
||||
return u8"YYC3";
|
||||
}
|
||||
|
||||
void YYCRule3::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
// Using utility structs
|
||||
using Shared::DupCmp::CKMaterialWrapper;
|
||||
using Shared::DupCmp::CKMaterialWrapperEqualTo;
|
||||
using Shared::DupCmp::CKMaterialWrapperHash;
|
||||
using Shared::DupCmp::CKMeshWrapper;
|
||||
using Shared::DupCmp::CKMeshWrapperEqualTo;
|
||||
using Shared::DupCmp::CKMeshWrapperHash;
|
||||
using Shared::DupCmp::CKTextureWrapper;
|
||||
using Shared::DupCmp::CKTextureWrapperEqualTo;
|
||||
using Shared::DupCmp::CKTextureWrapperHash;
|
||||
|
||||
// Check textures
|
||||
std::unordered_multiset<CKTextureWrapper, CKTextureWrapperHash, CKTextureWrapperEqualTo> textures;
|
||||
for (auto* tex : level.GetTextures()) {
|
||||
textures.emplace(CKTextureWrapper(tex));
|
||||
}
|
||||
// Show result
|
||||
for (auto it = textures.begin(); it != textures.end();) {
|
||||
size_t count = textures.count(*it);
|
||||
|
||||
// all count elements have equivalent keys
|
||||
if (count > 1) {
|
||||
std::vector<O::CKTexture*> dup_texs;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
dup_texs.emplace_back(it->GetTexture());
|
||||
++it;
|
||||
}
|
||||
|
||||
reporter.FormatInfo(u8"Some textures are visually identical. Please consider merging them to reduce the final map size. "
|
||||
u8"These textures are: %s.",
|
||||
Shared::Utility::QuoteObjectNames(dup_texs.begin(), dup_texs.end()).c_str());
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// Check materials
|
||||
std::unordered_multiset<CKMaterialWrapper, CKMaterialWrapperHash, CKMaterialWrapperEqualTo> materials;
|
||||
for (auto* mat : level.GetMaterials()) {
|
||||
materials.emplace(CKMaterialWrapper(mat));
|
||||
}
|
||||
// Show result
|
||||
for (auto it = materials.begin(); it != materials.end();) {
|
||||
size_t count = materials.count(*it);
|
||||
|
||||
// all count elements have equivalent keys
|
||||
if (count > 1) {
|
||||
std::vector<O::CKMaterial*> dup_mtls;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
dup_mtls.emplace_back(it->GetMaterial());
|
||||
++it;
|
||||
}
|
||||
|
||||
reporter.FormatInfo(u8"Some materials are visually identical. Please consider merging them to reduce the final map size. "
|
||||
u8"These materials are: %s.",
|
||||
Shared::Utility::QuoteObjectNames(dup_mtls.begin(), dup_mtls.end()).c_str());
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// Check meshes
|
||||
std::unordered_multiset<CKMeshWrapper, CKMeshWrapperHash, CKMeshWrapperEqualTo> meshes;
|
||||
for (auto* mesh : level.GetMeshes()) {
|
||||
meshes.emplace(CKMeshWrapper(mesh));
|
||||
}
|
||||
// Show result
|
||||
for (auto it = meshes.begin(); it != meshes.end();) {
|
||||
size_t count = meshes.count(*it);
|
||||
|
||||
// all count elements have equivalent keys
|
||||
if (count > 1) {
|
||||
std::vector<O::CKMesh*> dup_meshes;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
dup_meshes.emplace_back(it->GetMesh());
|
||||
++it;
|
||||
}
|
||||
|
||||
reporter.FormatInfo(u8"Some meshes are visually identical. Please consider merging them to reduce the final map size. "
|
||||
u8"These meshes are: %s.",
|
||||
Shared::Utility::QuoteObjectNames(dup_meshes.begin(), dup_meshes.end()).c_str());
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region YYC Rule 4
|
||||
|
||||
YYCRule4::YYCRule4() : Rule::IRule() {}
|
||||
|
||||
YYCRule4::~YYCRule4() {}
|
||||
|
||||
std::u8string_view YYCRule4::GetRuleName() const {
|
||||
return u8"YYC4";
|
||||
}
|
||||
|
||||
void YYCRule4::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
// Build lowercase texture name set first.
|
||||
std::set<std::u8string> opaque_texs;
|
||||
for (const auto* tex_name : Shared::Name::Texture::OPAQUE_TEXS) {
|
||||
opaque_texs.emplace(strop::to_lower(tex_name));
|
||||
}
|
||||
std::set<std::u8string> transparent_texs;
|
||||
for (const auto* tex_name : Shared::Name::Texture::TRANSPARENT_TEXS) {
|
||||
transparent_texs.emplace(strop::to_lower(tex_name));
|
||||
}
|
||||
|
||||
// Check texture one by one
|
||||
for (auto& tex : level.GetTextures()) {
|
||||
auto tex_filename = Shared::Utility::ExtractTextureFileName(tex);
|
||||
if (!tex_filename.has_value()) continue;
|
||||
|
||||
auto lower_tex_filename = strop::to_lower(tex_filename.value());
|
||||
if (opaque_texs.contains(lower_tex_filename)) {
|
||||
if (tex->GetVideoFormat() != V::VX_PIXELFORMAT::_16_ARGB1555) {
|
||||
reporter.FormatWarning(u8"Texture %s is Ballance opaque texture. But its video format is not ARGB1555. "
|
||||
u8"This is mismatched with vanilla Ballance.",
|
||||
Shared::Utility::QuoteObjectName(tex).c_str());
|
||||
}
|
||||
} else if (transparent_texs.contains(lower_tex_filename)) {
|
||||
if (tex->GetVideoFormat() != V::VX_PIXELFORMAT::_32_ARGB8888) {
|
||||
reporter.FormatWarning(u8"Texture %s is Ballance transparent texture. But its video format is not ARGB8888. "
|
||||
u8"This is mismatched with vanilla Ballance.",
|
||||
Shared::Utility::QuoteObjectName(tex).c_str());
|
||||
}
|
||||
} else {
|
||||
switch (tex->GetVideoFormat()) {
|
||||
case V::VX_PIXELFORMAT::_16_ARGB1555:
|
||||
// Do nothing.
|
||||
break;
|
||||
case V::VX_PIXELFORMAT::_32_ARGB8888:
|
||||
reporter.FormatWarning(u8"Texture %s is not Ballance texture. Its video format is ARGB8888. "
|
||||
u8"This may cause useless performance consumption if there is no transparent inside it. "
|
||||
u8"Please check whether this is essential.",
|
||||
Shared::Utility::QuoteObjectName(tex).c_str());
|
||||
break;
|
||||
default:
|
||||
reporter.FormatWarning(
|
||||
u8"Texture %s is not Ballance texture. Its video format is not ARGB1555 or ARGB8888. "
|
||||
u8"This is mismatched with vanilla Ballance. "
|
||||
u8"Please set it to ARGB1555 for opaque texture, or ARGB8888 for transaprent texture, except special scenario.",
|
||||
Shared::Utility::QuoteObjectName(tex).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
74
Ballance/BMapInspector/Ruleset/YYCRules.hpp
Normal file
74
Ballance/BMapInspector/Ruleset/YYCRules.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @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 Rule::IRule {
|
||||
public:
|
||||
YYCRule1();
|
||||
virtual ~YYCRule1();
|
||||
YYCC_DELETE_COPY_MOVE(YYCRule1)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() 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 Rule::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;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief YYC12345 Rule 3
|
||||
* @details
|
||||
* Exactly same mesh, material and texture can be merged.
|
||||
*/
|
||||
class YYCRule3 : public Rule::IRule {
|
||||
public:
|
||||
YYCRule3();
|
||||
virtual ~YYCRule3();
|
||||
YYCC_DELETE_COPY_MOVE(YYCRule3)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief YYC12345 Rule 4
|
||||
* @details
|
||||
* \li Check the video format for opaque and transparent texture respectively.
|
||||
* \li Warning for video format which is not used by vanilla Ballance.
|
||||
* \li Warning for transparent used video format in non-Ballance textures to conserve resources.
|
||||
*/
|
||||
class YYCRule4 : public Rule::IRule {
|
||||
public:
|
||||
YYCRule4();
|
||||
virtual ~YYCRule4();
|
||||
YYCC_DELETE_COPY_MOVE(YYCRule4)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
}
|
||||
224
Ballance/BMapInspector/Ruleset/ZZQRules.cpp
Normal file
224
Ballance/BMapInspector/Ruleset/ZZQRules.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#include "ZZQRules.hpp"
|
||||
#include "Shared/Utility.hpp"
|
||||
#include "Shared/Name.hpp"
|
||||
#include "Shared/Sector.hpp"
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region ZZQ Rule 1
|
||||
|
||||
ZZQRule1::ZZQRule1() : Rule::IRule() {}
|
||||
|
||||
ZZQRule1::~ZZQRule1() {}
|
||||
|
||||
std::u8string_view ZZQRule1::GetRuleName() const {
|
||||
return u8"ZZQ1";
|
||||
}
|
||||
|
||||
void ZZQRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
|
||||
// We get "Phys_FloorStopper" group first.
|
||||
auto* phys_floorstopper = Shared::Utility::FetchGroup(ctx, Shared::Name::Group::PHYS_FLOORSTOPPER);
|
||||
if (phys_floorstopper == nullptr) return;
|
||||
// We iterate all object grouped into it.
|
||||
auto group_3dobjects = Shared::Utility::Iter3dObjects(phys_floorstopper);
|
||||
|
||||
// Show the first object if it have.
|
||||
if (!group_3dobjects.empty()) {
|
||||
auto* first_3dobjects = group_3dobjects.front();
|
||||
reporter.FormatInfo(u8"Object %s is the first object grouped into %s. "
|
||||
u8"It is the only stopper which can make sound in game.",
|
||||
Shared::Utility::QuoteObjectName(first_3dobjects).c_str(),
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PHYS_FLOORSTOPPER).c_str());
|
||||
}
|
||||
|
||||
// Warning for other objects
|
||||
for (size_t i = 1; i < group_3dobjects.size(); ++i) {
|
||||
auto* other_3dobject = group_3dobjects[i];
|
||||
reporter.FormatWarning(u8"Object %s is grouped into %s but it is not the only object. "
|
||||
u8"This will cause it can not make sound in game. Please confirm this is by your intention. "
|
||||
u8"If you want it can make sound, please join it into the first object located in that group.",
|
||||
Shared::Utility::QuoteObjectName(other_3dobject).c_str(),
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PHYS_FLOORSTOPPER).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ZZQ Rule 2
|
||||
|
||||
ZZQRule2::ZZQRule2() : Rule::IRule() {}
|
||||
|
||||
ZZQRule2::~ZZQRule2() {}
|
||||
|
||||
std::u8string_view ZZQRule2::GetRuleName() const {
|
||||
return u8"ZZQ2";
|
||||
}
|
||||
|
||||
void ZZQRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
Shared::Sector::SectorNameBuilder builder;
|
||||
|
||||
// Extract group objects info
|
||||
std::vector<std::set<O::CK3dObject*>> sector_objects;
|
||||
for (L::CKDWORD i = Shared::Sector::MIN_SECTOR; i <= Shared::Sector::MAX_SECTOR; ++i) {
|
||||
// Prepare inserted object set.
|
||||
std::set<O::CK3dObject*> object_set;
|
||||
|
||||
// Build name first with special treat for sector 9
|
||||
// and fill objects into set.
|
||||
if (i != 9) {
|
||||
auto sector_name = builder.get_name(i);
|
||||
auto* sector = Shared::Utility::FetchGroup(ctx, sector_name.c_str());
|
||||
if (sector == nullptr) break;
|
||||
|
||||
auto group_3dobjects = Shared::Utility::Iter3dObjects(sector);
|
||||
for (auto* group_3dobject : group_3dobjects) {
|
||||
object_set.emplace(group_3dobject);
|
||||
}
|
||||
} else {
|
||||
auto sector_names = builder.get_sector9_names();
|
||||
auto* legacy_sector = Shared::Utility::FetchGroup(ctx, sector_names.legacy_name.c_str());
|
||||
auto* intuitive_sector = Shared::Utility::FetchGroup(ctx, sector_names.intuitive_name.c_str());
|
||||
if (legacy_sector == nullptr && intuitive_sector == nullptr) break;
|
||||
|
||||
if (legacy_sector != nullptr) {
|
||||
auto group_3dobjects = Shared::Utility::Iter3dObjects(legacy_sector);
|
||||
for (auto* group_3dobject : group_3dobjects) {
|
||||
object_set.emplace(group_3dobject);
|
||||
}
|
||||
}
|
||||
if (intuitive_sector != nullptr) {
|
||||
auto group_3dobjects = Shared::Utility::Iter3dObjects(intuitive_sector);
|
||||
for (auto* group_3dobject : group_3dobjects) {
|
||||
object_set.emplace(group_3dobject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert object set
|
||||
sector_objects.emplace_back(std::move(object_set));
|
||||
}
|
||||
|
||||
// Check the intersection one by one
|
||||
for (size_t i = 0; i < sector_objects.size(); ++i) {
|
||||
for (size_t j = i + 1; j < sector_objects.size(); ++j) {
|
||||
// Fetch 2 set repsectively.
|
||||
const auto& left_sector = sector_objects[i];
|
||||
const auto& right_sector = sector_objects[j];
|
||||
// Check duplicated objects
|
||||
std::vector<O::CK3dObject*> intersection;
|
||||
std::set_intersection(left_sector.begin(),
|
||||
left_sector.end(),
|
||||
right_sector.begin(),
|
||||
right_sector.end(),
|
||||
std::back_inserter(intersection));
|
||||
|
||||
// Output if there is intersection
|
||||
if (!intersection.empty()) {
|
||||
// Get sector index.
|
||||
auto left_sector_idx = static_cast<L::CKDWORD>(i + 1);
|
||||
auto right_sector_idx = static_cast<L::CKDWORD>(j + 1);
|
||||
|
||||
// Output result.
|
||||
reporter.FormatWarning(u8"Some objects are grouped into sector %" PRIuCKDWORD " and sector %" PRIuCKDWORD
|
||||
" represented group bothly. This is not allowed. These objects are: %s.",
|
||||
left_sector_idx,
|
||||
right_sector_idx,
|
||||
Shared::Utility::QuoteObjectNames(intersection.begin(), intersection.end()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ZZQ Rule 3
|
||||
|
||||
ZZQRule3::ZZQRule3() : Rule::IRule() {}
|
||||
|
||||
ZZQRule3::~ZZQRule3() {}
|
||||
|
||||
std::u8string_view ZZQRule3::GetRuleName() const {
|
||||
return u8"ZZQ3";
|
||||
}
|
||||
|
||||
void ZZQRule3::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
Shared::Sector::SectorNameBuilder builder;
|
||||
|
||||
auto* level_start = Shared::Utility::FetchGroup(ctx, Shared::Name::Group::PS_LEVELSTART);
|
||||
if (level_start == nullptr) {
|
||||
reporter.FormatError(u8"Incomplete level: can not find %s group.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PS_LEVELSTART).c_str());
|
||||
} else {
|
||||
switch (level_start->GetObjectCount()) {
|
||||
case 0:
|
||||
reporter.FormatError(u8"Incomplete level: there is no object grouped into %s group.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PS_LEVELSTART).c_str());
|
||||
break;
|
||||
case 1:
|
||||
// OK. Do nothing.
|
||||
break;
|
||||
default:
|
||||
reporter.FormatError(u8"Bad level: there are more than one objects grouped into %s group.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PS_LEVELSTART).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto* level_end = Shared::Utility::FetchGroup(ctx, Shared::Name::Group::PE_LEVELENDE);
|
||||
if (level_end == nullptr) {
|
||||
reporter.FormatError(u8"Incomplete level: can not find %s group.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PE_LEVELENDE).c_str());
|
||||
} else {
|
||||
switch (level_end->GetObjectCount()) {
|
||||
case 0:
|
||||
reporter.FormatError(u8"Incomplete level: there is no object grouped into %s group.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PE_LEVELENDE).c_str());
|
||||
break;
|
||||
case 1:
|
||||
// OK. Do nothing.
|
||||
break;
|
||||
default:
|
||||
reporter.FormatError(u8"Bad level: there are more than one objects grouped into %s group.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PE_LEVELENDE).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto* check_points = Shared::Utility::FetchGroup(ctx, Shared::Name::Group::PC_CHECKPOINTS);
|
||||
if (check_points == nullptr) {
|
||||
reporter.FormatWarning(u8"Can not find %s group. This will cause bad render of particle at the level start point.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PC_CHECKPOINTS).c_str());
|
||||
}
|
||||
|
||||
auto* reset_points = Shared::Utility::FetchGroup(ctx, Shared::Name::Group::PR_RESETPOINTS);
|
||||
if (reset_points == nullptr) {
|
||||
reporter.FormatError(u8"Incomplete level: can not find %s group.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PR_RESETPOINTS).c_str());
|
||||
} else {
|
||||
if (reset_points->GetObjectCount() == 0) {
|
||||
reporter.FormatError(u8"Incomplete level: there is no object grouped into %s group.",
|
||||
Shared::Utility::QuoteText(Shared::Name::Group::PR_RESETPOINTS).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
auto sector1_name = builder.get_name(1);
|
||||
auto* sector1 = Shared::Utility::FetchGroup(ctx, sector1_name.c_str());
|
||||
if (sector1 == nullptr) {
|
||||
reporter.FormatError(u8"Incomplete level: can not find %s group.", Shared::Utility::QuoteText(sector1_name).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
64
Ballance/BMapInspector/Ruleset/ZZQRules.hpp
Normal file
64
Ballance/BMapInspector/Ruleset/ZZQRules.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief ZZQ Rule 1
|
||||
* @details
|
||||
* Only the first object grouped into "Phys_FloorStopper" can make sound in game.
|
||||
* So it would be better to make "Phys_FloorStopper" only have one item.
|
||||
*
|
||||
* At the same time, show which object is the first object in "Phys_FloorStopper"
|
||||
* to know which object can make sound in game,
|
||||
* if mapper require the stopper which can not make sound them by design.
|
||||
*/
|
||||
class ZZQRule1 : public Rule::IRule {
|
||||
public:
|
||||
ZZQRule1();
|
||||
virtual ~ZZQRule1();
|
||||
YYCC_DELETE_COPY_MOVE(ZZQRule1)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ZZQ Rule 2
|
||||
* @details
|
||||
* The Ballance should only be included only one group.
|
||||
* This rule will check whether there is intersection between different sector group.
|
||||
*/
|
||||
class ZZQRule2 : public Rule::IRule {
|
||||
public:
|
||||
ZZQRule2();
|
||||
virtual ~ZZQRule2();
|
||||
YYCC_DEFAULT_COPY_MOVE(ZZQRule2)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ZZQ Rule 3
|
||||
* @details
|
||||
* A minimalist level must contains following items:
|
||||
* \li One start point.
|
||||
* \li One end point (spaceship).
|
||||
* \li One reset point.
|
||||
* \li "Sector_01" group.
|
||||
*/
|
||||
class ZZQRule3 : public Rule::IRule {
|
||||
public:
|
||||
ZZQRule3();
|
||||
virtual ~ZZQRule3();
|
||||
YYCC_DELETE_COPY_MOVE(ZZQRule3)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user