feat: fully refactor BMapInspector rule set for better layout
This commit is contained in:
@@ -101,14 +101,16 @@ static void CheckRules(BMapInspector::Cli::Args& args, BMapInspector::Map::Level
|
||||
BMapInspector::Reporter::Reporter reporter;
|
||||
|
||||
// Get rule collection
|
||||
BMapInspector::Rule::Ruleset ruleset;
|
||||
BMapInspector::Rule::RuleCollection rule_collection;
|
||||
// Show rule infos
|
||||
std::cout << strop::printf(u8"Total %" PRIuSIZET " rule(s) are loaded.", ruleset.GetRuleCount()) << std::endl
|
||||
std::cout << strop::printf(u8"Total %" PRIuSIZET " rule(s) are loaded.", rule_collection.GetRuleCount()) << std::endl
|
||||
<< u8"Check may take few minutes. Please do not close this console..." << std::endl;
|
||||
|
||||
// Check rules one by one
|
||||
for (auto* rule : ruleset.GetRules()) {
|
||||
for (auto* rule : rule_collection.GetRules()) {
|
||||
reporter.EnterRule(rule->GetRuleName());
|
||||
rule->Check(reporter, level);
|
||||
reporter.LeaveRule();
|
||||
}
|
||||
|
||||
// Show report conclusion
|
||||
|
||||
@@ -11,15 +11,18 @@ PRIVATE
|
||||
Map.cpp
|
||||
Rule.cpp
|
||||
# Rules
|
||||
Rule/Shared.cpp
|
||||
Rule/GpRules.cpp
|
||||
Rule/ChirsRules.cpp
|
||||
Rule/YYCRules.cpp
|
||||
Rule/ZZQRules.cpp
|
||||
Rule/BBugRules.cpp
|
||||
Rule/SOneRules.cpp
|
||||
Rule/SSBRules.cpp
|
||||
Rule/LXRules.cpp
|
||||
Ruleset/Shared/Utility.cpp
|
||||
Ruleset/Shared/Name.cpp
|
||||
Ruleset/Shared/Sector.cpp
|
||||
Ruleset/Shared/DupCmp.cpp
|
||||
Ruleset/GpRules.cpp
|
||||
Ruleset/ChirsRules.cpp
|
||||
Ruleset/YYCRules.cpp
|
||||
Ruleset/ZZQRules.cpp
|
||||
Ruleset/BBugRules.cpp
|
||||
Ruleset/SOneRules.cpp
|
||||
Ruleset/SSBRules.cpp
|
||||
Ruleset/LXRules.cpp
|
||||
)
|
||||
# Setup headers
|
||||
target_sources(BMapInspector
|
||||
@@ -33,15 +36,18 @@ FILES
|
||||
Map.hpp
|
||||
Rule.hpp
|
||||
# Rules
|
||||
Rule/Shared.hpp
|
||||
Rule/GpRules.hpp
|
||||
Rule/ChirsRules.hpp
|
||||
Rule/YYCRules.hpp
|
||||
Rule/ZZQRules.hpp
|
||||
Rule/BBugRules.hpp
|
||||
Rule/SOneRules.hpp
|
||||
Rule/SSBRules.hpp
|
||||
Rule/LXRules.hpp
|
||||
Ruleset/Shared/Utility.hpp
|
||||
Ruleset/Shared/Name.hpp
|
||||
Ruleset/Shared/Sector.hpp
|
||||
Ruleset/Shared/DupCmp.hpp
|
||||
Ruleset/GpRules.hpp
|
||||
Ruleset/ChirsRules.hpp
|
||||
Ruleset/YYCRules.hpp
|
||||
Ruleset/ZZQRules.hpp
|
||||
Ruleset/BBugRules.hpp
|
||||
Ruleset/SOneRules.hpp
|
||||
Ruleset/SSBRules.hpp
|
||||
Ruleset/LXRules.hpp
|
||||
)
|
||||
# Setup header infomation
|
||||
target_include_directories(BMapInspector
|
||||
|
||||
@@ -1,54 +1,70 @@
|
||||
#include "Reporter.hpp"
|
||||
#include <yycc/string/op.hpp>
|
||||
#include <cstdarg>
|
||||
#include <stdexcept>
|
||||
|
||||
using BMapInspector::Utils::ReportLevel;
|
||||
namespace strop = yycc::string::op;
|
||||
|
||||
namespace BMapInspector::Reporter {
|
||||
|
||||
#pragma region Reporter
|
||||
|
||||
Reporter::Reporter() {}
|
||||
Reporter::Reporter() : current_rule(std::nullopt), reports() {}
|
||||
|
||||
Reporter::~Reporter() {}
|
||||
|
||||
void Reporter::AddReport(Utils::ReportLevel level, const std::u8string_view &rule, const std::u8string_view &content) {
|
||||
this->reports.emplace_back(Report{
|
||||
.level = level,
|
||||
.rule = std::u8string(rule),
|
||||
.content = std::u8string(content),
|
||||
});
|
||||
void Reporter::EnterRule(const std::u8string_view &rule) {
|
||||
if (this->current_rule.has_value()) throw std::logic_error("can not enter rule multiple times");
|
||||
else this->current_rule = std::u8string(rule);
|
||||
}
|
||||
|
||||
void Reporter::WriteInfo(const std::u8string_view &rule, const std::u8string_view &content) {
|
||||
this->AddReport(Utils::ReportLevel::Info, rule, content);
|
||||
void Reporter::LeaveRule() {
|
||||
if (this->current_rule.has_value()) this->current_rule = std::nullopt;
|
||||
else throw std::logic_error("can not leave rule without any existing rule");
|
||||
}
|
||||
|
||||
void Reporter::FormatInfo(const std::u8string_view &rule, const char8_t *fmt, ...) {
|
||||
void Reporter::AddReport(ReportLevel level, const std::u8string_view &content) {
|
||||
if (this->current_rule.has_value()) {
|
||||
this->reports.emplace_back(Report{
|
||||
.level = level,
|
||||
.rule = std::u8string(this->current_rule.value()),
|
||||
.content = std::u8string(content),
|
||||
});
|
||||
} else {
|
||||
throw std::logic_error("can not add report without any rule scope");
|
||||
}
|
||||
}
|
||||
|
||||
void Reporter::WriteInfo(const std::u8string_view &content) {
|
||||
this->AddReport(ReportLevel::Info, content);
|
||||
}
|
||||
|
||||
void Reporter::FormatInfo(const char8_t *fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, fmt);
|
||||
this->WriteInfo(rule, strop::vprintf(fmt, argptr));
|
||||
this->WriteInfo(strop::vprintf(fmt, argptr));
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void Reporter::WriteWarning(const std::u8string_view &rule, const std::u8string_view &content) {
|
||||
this->AddReport(Utils::ReportLevel::Warning, rule, content);
|
||||
void Reporter::WriteWarning(const std::u8string_view &content) {
|
||||
this->AddReport(ReportLevel::Warning, content);
|
||||
}
|
||||
void Reporter::FormatWarning(const std::u8string_view &rule, const char8_t *fmt, ...) {
|
||||
void Reporter::FormatWarning(const char8_t *fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, fmt);
|
||||
this->WriteWarning(rule, strop::vprintf(fmt, argptr));
|
||||
this->WriteWarning(strop::vprintf(fmt, argptr));
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void Reporter::WriteError(const std::u8string_view &rule, const std::u8string_view &content) {
|
||||
this->AddReport(Utils::ReportLevel::Error, rule, content);
|
||||
void Reporter::WriteError(const std::u8string_view &content) {
|
||||
this->AddReport(ReportLevel::Error, content);
|
||||
}
|
||||
|
||||
void Reporter::FormatError(const std::u8string_view &rule, const char8_t *fmt, ...) {
|
||||
void Reporter::FormatError(const char8_t *fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, fmt);
|
||||
this->WriteError(rule, strop::vprintf(fmt, argptr));
|
||||
this->WriteError(strop::vprintf(fmt, argptr));
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
@@ -56,13 +72,13 @@ namespace BMapInspector::Reporter {
|
||||
ReporterDigest digest{.cnt_err = 0, .cnt_warn = 0, .cnt_info = 0};
|
||||
for (const auto &report : this->reports) {
|
||||
switch (report.level) {
|
||||
case Utils::ReportLevel::Error:
|
||||
case ReportLevel::Error:
|
||||
++digest.cnt_err;
|
||||
break;
|
||||
case Utils::ReportLevel::Warning:
|
||||
case ReportLevel::Warning:
|
||||
++digest.cnt_warn;
|
||||
break;
|
||||
case Utils::ReportLevel::Info:
|
||||
case ReportLevel::Info:
|
||||
++digest.cnt_info;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace BMapInspector::Reporter {
|
||||
|
||||
@@ -26,22 +27,27 @@ namespace BMapInspector::Reporter {
|
||||
~Reporter();
|
||||
YYCC_DEFAULT_COPY_MOVE(Reporter)
|
||||
|
||||
public:
|
||||
void EnterRule(const std::u8string_view& rule);
|
||||
void LeaveRule();
|
||||
|
||||
private:
|
||||
void AddReport(Utils::ReportLevel level, const std::u8string_view& rule, const std::u8string_view& content);
|
||||
void AddReport(Utils::ReportLevel level, const std::u8string_view& content);
|
||||
|
||||
public:
|
||||
void WriteInfo(const std::u8string_view& rule, const std::u8string_view& content);
|
||||
void FormatInfo(const std::u8string_view& rule, const char8_t* fmt, ...);
|
||||
void WriteWarning(const std::u8string_view& rule, const std::u8string_view& content);
|
||||
void FormatWarning(const std::u8string_view& rule, const char8_t* fmt, ...);
|
||||
void WriteError(const std::u8string_view& rule, const std::u8string_view& content);
|
||||
void FormatError(const std::u8string_view& rule, const char8_t* fmt, ...);
|
||||
void WriteInfo(const std::u8string_view& content);
|
||||
void FormatInfo(const char8_t* fmt, ...);
|
||||
void WriteWarning(const std::u8string_view& content);
|
||||
void FormatWarning(const char8_t* fmt, ...);
|
||||
void WriteError(const std::u8string_view& content);
|
||||
void FormatError(const char8_t* fmt, ...);
|
||||
|
||||
public:
|
||||
ReporterDigest GetDigest() const;
|
||||
const std::vector<Report>& GetReports() const;
|
||||
|
||||
private:
|
||||
std::optional<std::u8string> current_rule;
|
||||
std::vector<Report> reports;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "Rule.hpp"
|
||||
|
||||
#include "Rule/GpRules.hpp"
|
||||
#include "Rule/ChirsRules.hpp"
|
||||
#include "Rule/YYCRules.hpp"
|
||||
#include "Rule/BBugRules.hpp"
|
||||
#include "Rule/ZZQRules.hpp"
|
||||
#include "Rule/SOneRules.hpp"
|
||||
#include "Rule/SSBRules.hpp"
|
||||
#include "Rule/LXRules.hpp"
|
||||
#include "Ruleset/GpRules.hpp"
|
||||
#include "Ruleset/ChirsRules.hpp"
|
||||
#include "Ruleset/YYCRules.hpp"
|
||||
#include "Ruleset/BBugRules.hpp"
|
||||
#include "Ruleset/ZZQRules.hpp"
|
||||
#include "Ruleset/SOneRules.hpp"
|
||||
#include "Ruleset/SSBRules.hpp"
|
||||
#include "Ruleset/LXRules.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
|
||||
@@ -19,44 +19,44 @@ namespace BMapInspector::Rule {
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Ruleset
|
||||
#pragma region RuleCollection
|
||||
|
||||
Ruleset::Ruleset() : rules() {
|
||||
RuleCollection::RuleCollection() : rules() {
|
||||
// Add rule into list.
|
||||
rules.emplace_back(new GpRule1());
|
||||
rules.emplace_back(new GpRule2());
|
||||
rules.emplace_back(new ChirsRule1());
|
||||
rules.emplace_back(new YYCRule1());
|
||||
rules.emplace_back(new YYCRule2());
|
||||
rules.emplace_back(new YYCRule3());
|
||||
rules.emplace_back(new YYCRule4());
|
||||
rules.emplace_back(new BBugRule1());
|
||||
rules.emplace_back(new BBugRule2());
|
||||
rules.emplace_back(new BBugRule3());
|
||||
rules.emplace_back(new ZZQRule1());
|
||||
rules.emplace_back(new ZZQRule2());
|
||||
rules.emplace_back(new ZZQRule3());
|
||||
rules.emplace_back(new SOneRule1());
|
||||
rules.emplace_back(new SSBRule1());
|
||||
rules.emplace_back(new LXRule1());
|
||||
rules.emplace_back(new Ruleset::GpRule1());
|
||||
rules.emplace_back(new Ruleset::GpRule2());
|
||||
rules.emplace_back(new Ruleset::ChirsRule1());
|
||||
rules.emplace_back(new Ruleset::YYCRule1());
|
||||
rules.emplace_back(new Ruleset::YYCRule2());
|
||||
rules.emplace_back(new Ruleset::YYCRule3());
|
||||
rules.emplace_back(new Ruleset::YYCRule4());
|
||||
rules.emplace_back(new Ruleset::BBugRule1());
|
||||
rules.emplace_back(new Ruleset::BBugRule2());
|
||||
rules.emplace_back(new Ruleset::BBugRule3());
|
||||
rules.emplace_back(new Ruleset::ZZQRule1());
|
||||
rules.emplace_back(new Ruleset::ZZQRule2());
|
||||
rules.emplace_back(new Ruleset::ZZQRule3());
|
||||
rules.emplace_back(new Ruleset::SOneRule1());
|
||||
rules.emplace_back(new Ruleset::SSBRule1());
|
||||
rules.emplace_back(new Ruleset::LXRule1());
|
||||
// Add more rules...
|
||||
}
|
||||
|
||||
Ruleset::~Ruleset() {
|
||||
RuleCollection::~RuleCollection() {
|
||||
// Free rule from list.
|
||||
for (const auto* rule : this->rules) {
|
||||
delete rule;
|
||||
}
|
||||
}
|
||||
|
||||
size_t Ruleset::GetRuleCount() const {
|
||||
size_t RuleCollection::GetRuleCount() const {
|
||||
return this->rules.size();
|
||||
}
|
||||
|
||||
const std::vector<IRule*>& Ruleset::GetRules() const {
|
||||
const std::vector<IRule*>& RuleCollection::GetRules() const {
|
||||
return this->rules;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
} // namespace BMapInspector::RuleCollection
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
|
||||
/**
|
||||
* @brief The interface of a rule.
|
||||
*/
|
||||
class IRule {
|
||||
public:
|
||||
IRule();
|
||||
@@ -21,18 +24,21 @@ namespace BMapInspector::Rule {
|
||||
virtual void Check(Reporter::Reporter& reporter, Map::Level& level) const = 0;
|
||||
};
|
||||
|
||||
class Ruleset {
|
||||
/**
|
||||
* @brief A collection of rules.
|
||||
*/
|
||||
class RuleCollection {
|
||||
public:
|
||||
Ruleset();
|
||||
~Ruleset();
|
||||
YYCC_DELETE_COPY_MOVE(Ruleset)
|
||||
RuleCollection();
|
||||
~RuleCollection();
|
||||
YYCC_DELETE_COPY_MOVE(RuleCollection)
|
||||
|
||||
public:
|
||||
size_t GetRuleCount() const;
|
||||
const std::vector<IRule *> &GetRules() const;
|
||||
const std::vector<IRule*>& GetRules() const;
|
||||
|
||||
private:
|
||||
std::vector<IRule *> rules;
|
||||
std::vector<IRule*> rules;
|
||||
};
|
||||
|
||||
} // namespace BMapInspector::Ruleset
|
||||
} // namespace BMapInspector::Rule
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
#include "ChirsRules.hpp"
|
||||
#include "Shared.hpp"
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
|
||||
// Reference: https://tieba.baidu.com/p/5913556704
|
||||
|
||||
#pragma region Chirs Rule 1
|
||||
|
||||
constexpr char8_t CHIRS1[] = u8"CHIRS1";
|
||||
|
||||
ChirsRule1::ChirsRule1() : IRule() {}
|
||||
|
||||
ChirsRule1::~ChirsRule1() {}
|
||||
|
||||
std::u8string_view ChirsRule1::GetRuleName() const {
|
||||
return CHIRS1;
|
||||
}
|
||||
void ChirsRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
constexpr char8_t MTL_NAME[] = u8"Laterne_Verlauf";
|
||||
auto* ctx = level.GetCKContext();
|
||||
|
||||
// Fetch Laterne_Verlauf first
|
||||
auto* latern = Shared::FetchMaterial(ctx, MTL_NAME);
|
||||
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.WriteWarning(CHIRS1,
|
||||
u8R"(Find a material named "Laterne_Verlauf" but it doesn't have associated texture. )"
|
||||
u8R"(It occupies the magic material "Laterne_Verlauf" which affect the ray of latern in game. Please confirm this is your intention.)");
|
||||
} else {
|
||||
if (!Shared::CheckTextureFileName(latern_tex, Shared::TextureNames::LATERNE_VERLAUF)) {
|
||||
reporter.WriteWarning(CHIRS1,
|
||||
u8R"(Find a material named "Laterne_Verlauf" but its texture is not "Laterne_Verlauf.tga". )"
|
||||
u8R"(It occupies the magic material "Laterne_Verlauf" which affect the ray of latern in game. Please confirm this is your intention.)");
|
||||
}
|
||||
}
|
||||
|
||||
// Report warning if there is multiple Laterne_Verlauf material.
|
||||
auto next_latern = ctx->GetObjectByNameAndClass(MTL_NAME, C::CK_CLASSID::CKCID_MATERIAL, latern);
|
||||
if (next_latern != nullptr) {
|
||||
reporter.WriteWarning(
|
||||
CHIRS1,
|
||||
u8R"(There are multiple materials named "Laterne_Verlauf". This will cause the disappearance of some latern's rays.)");
|
||||
}
|
||||
|
||||
// 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_NAME)) continue;
|
||||
|
||||
auto other_mtl_tex = other_mtl->GetTexture();
|
||||
if (other_mtl_tex == nullptr) continue;
|
||||
|
||||
if (Shared::CheckTextureFileName(other_mtl_tex, Shared::TextureNames::LATERNE_VERLAUF)) {
|
||||
reporter.FormatWarning(
|
||||
CHIRS1,
|
||||
u8R"(Find material %s referring texture "Laterne_Verlauf.tga", but its name is not "Laterne_Verlauf". )"
|
||||
u8R"(Please confirm the usage of this material. If it is used as "Laterne_Verlauf", please rename it into "Laterne_Verlauf" to have correct latern ray.)",
|
||||
Shared::QuoteObjectName(other_mtl).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "SOneRules.hpp"
|
||||
#include "Shared.hpp"
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
|
||||
#pragma region SOne Rule 1
|
||||
|
||||
constexpr char8_t SONE1[] = u8"SONE1";
|
||||
|
||||
SOneRule1::SOneRule1() : IRule() {}
|
||||
|
||||
SOneRule1::~SOneRule1() {}
|
||||
|
||||
std::u8string_view SOneRule1::GetRuleName() const {
|
||||
return SONE1;
|
||||
}
|
||||
|
||||
void SOneRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
auto physicalized_3dobjects = Shared::FetchPhysicalized3dObjects(ctx);
|
||||
|
||||
for (auto* physicalized_3dobject : physicalized_3dobjects) {
|
||||
auto* mesh = physicalized_3dobject->GetCurrentMesh();
|
||||
if (mesh == nullptr) {
|
||||
reporter.FormatError(
|
||||
SONE1,
|
||||
u8R"(Object %s is grouped into physicalization group, but it doesn't have any associated mesh. This will cause itself and following objects can not be physicalized.)",
|
||||
Shared::QuoteObjectName(physicalized_3dobject).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
@@ -1,700 +0,0 @@
|
||||
#include "YYCRules.hpp"
|
||||
#include "Shared.hpp"
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/macro/ptr_size_detector.hpp>
|
||||
#include <yycc/string/op.hpp>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#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::Rule {
|
||||
|
||||
#pragma region YYC Rule 1
|
||||
|
||||
constexpr char8_t YYC1[] = u8"YYC1";
|
||||
|
||||
YYCRule1::YYCRule1() : IRule() {}
|
||||
|
||||
YYCRule1::~YYCRule1() {}
|
||||
|
||||
std::u8string_view YYCRule1::GetRuleName() const {
|
||||
return YYC1;
|
||||
}
|
||||
|
||||
void YYCRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
|
||||
// We get "Phys_FloorRails" group first.
|
||||
auto* phys_floorrails = Shared::FetchGroup(ctx, Shared::GroupNames::PHYS_FLOORRAILS);
|
||||
if (phys_floorrails == nullptr) return;
|
||||
|
||||
// Create container holding smooth meshes
|
||||
std::set<O::CKMesh*> smooth_meshes;
|
||||
// We iterate all object grouped into it.
|
||||
auto group_3dobjects = Shared::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::IterMaterial(mesh);
|
||||
for (auto* mtl : mtls) {
|
||||
// Check whether all texture referred by this mesh are "Rail_Environment".
|
||||
auto texture = mtl->GetTexture();
|
||||
if (texture == nullptr) continue;
|
||||
if (!Shared::CheckTextureFileName(texture, Shared::TextureNames::RAIL_ENVIRONMENT)) {
|
||||
// No, this is not rail texture, throw error.
|
||||
reporter.FormatError(
|
||||
YYC1,
|
||||
u8R"(Object %s is grouped into Phys_FloorRails, but its texture %s (referred by mesh %s and material %s) seems not the rail texture. This will cause some parts of this object be smooth unexpectly.)",
|
||||
Shared::QuoteObjectName(group_3dobject).c_str(),
|
||||
Shared::QuoteObjectName(texture).c_str(),
|
||||
Shared::QuoteObjectName(mesh).c_str(),
|
||||
Shared::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(
|
||||
YYC1,
|
||||
u8R"(Object %s is not grouped into Phys_FloorRails, but some objects grouped into Phys_FloorRails refer its mesh %s. This will cause this object be smooth unexpectly.)",
|
||||
Shared::QuoteObjectName(obj).c_str(),
|
||||
Shared::QuoteObjectName(mesh).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region YYC Rule 2
|
||||
|
||||
constexpr char8_t YYC2[] = u8"YYC2";
|
||||
|
||||
YYCRule2::YYCRule2() : IRule() {}
|
||||
|
||||
YYCRule2::~YYCRule2() {}
|
||||
|
||||
std::u8string_view YYCRule2::GetRuleName() const {
|
||||
return YYC2;
|
||||
}
|
||||
|
||||
void YYCRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
auto physicalized_3dobjects = Shared::FetchPhysicalized3dObjects(ctx);
|
||||
|
||||
// Iterate all physicalized 3dobject
|
||||
for (auto* physicalized_3dobject : physicalized_3dobjects) {
|
||||
// Get its mesh
|
||||
auto* mesh = physicalized_3dobject->GetCurrentMesh();
|
||||
if (mesh == nullptr) continue;
|
||||
|
||||
// Create a bool vector with vertex count and false init value.
|
||||
std::vector<bool> used_vertex(mesh->GetVertexCount(), false);
|
||||
// Iterate all face and set their vertex as used.
|
||||
auto* face_indices = mesh->GetFaceIndices();
|
||||
for (L::CKDWORD face_idx = 0; face_idx < mesh->GetFaceCount(); ++face_idx) {
|
||||
used_vertex[face_indices[face_idx * 3]] = true;
|
||||
used_vertex[face_indices[face_idx * 3 + 1]] = true;
|
||||
used_vertex[face_indices[face_idx * 3 + 2]] = true;
|
||||
}
|
||||
// Check whether there is unused vertex
|
||||
auto has_unused_vertex = std::any_of(used_vertex.begin(), used_vertex.end(), [](bool v) { return v == false; });
|
||||
// If there is unused vertex, report error
|
||||
if (has_unused_vertex) {
|
||||
reporter.FormatError(
|
||||
YYC2,
|
||||
u8R"(Object %s is grouped into physicalization groups, and its referred mesh %s has isolated vertex. This will cause it can not be physicalized.)",
|
||||
Shared::QuoteObjectName(physicalized_3dobject).c_str(),
|
||||
Shared::QuoteObjectName(mesh).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region YYC Rule 3
|
||||
|
||||
/**
|
||||
* @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() : seed(FNV_OFFSET_BASIS) {}
|
||||
~Hasher() {}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Update this hash combiner with new hash.
|
||||
* @param h
|
||||
*/
|
||||
void combine(ValueType h) {
|
||||
this->seed ^= h;
|
||||
this->seed *= FNV_PRIME;
|
||||
}
|
||||
|
||||
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 region Primitive CK Hasher
|
||||
} // namespace BMapInspector::Rule
|
||||
|
||||
namespace std {
|
||||
using BMapInspector::Rule::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();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
#pragma endregion
|
||||
|
||||
#pragma region CKObject Hash and Equal
|
||||
|
||||
struct CKTextureHash {
|
||||
[[nodiscard]] size_t 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();
|
||||
}
|
||||
};
|
||||
|
||||
struct CKMaterialHash {
|
||||
[[nodiscard]] size_t 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();
|
||||
}
|
||||
};
|
||||
|
||||
struct CKMeshHash {
|
||||
[[nodiscard]] size_t 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();
|
||||
}
|
||||
};
|
||||
|
||||
struct CKTextureEqualTo {
|
||||
[[nodiscard]] bool 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;
|
||||
}
|
||||
};
|
||||
|
||||
struct CKMaterialEqualTo {
|
||||
[[nodiscard]] bool 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;
|
||||
}
|
||||
};
|
||||
|
||||
struct CKMeshEqualTo {
|
||||
[[nodiscard]] bool 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
|
||||
|
||||
class CKTextureWrapper {
|
||||
public:
|
||||
CKTextureWrapper(O::CKTexture* texture) : texture(texture), hasher(), hash(std::nullopt) {}
|
||||
~CKTextureWrapper() {}
|
||||
YYCC_DEFAULT_COPY_MOVE(CKTextureWrapper)
|
||||
|
||||
public:
|
||||
O::CKTexture* GetTexture() const { return texture; }
|
||||
size_t GetHash() const {
|
||||
if (!hash.has_value()) hash = hasher(texture);
|
||||
return hash.value();
|
||||
}
|
||||
|
||||
private:
|
||||
O::CKTexture* texture;
|
||||
CKTextureHash hasher;
|
||||
mutable std::optional<size_t> hash;
|
||||
};
|
||||
|
||||
class CKMaterialWrapper {
|
||||
public:
|
||||
CKMaterialWrapper(O::CKMaterial* material) : material(material), hasher(), hash(std::nullopt) {}
|
||||
~CKMaterialWrapper() {}
|
||||
YYCC_DEFAULT_COPY_MOVE(CKMaterialWrapper)
|
||||
|
||||
public:
|
||||
O::CKMaterial* GetMaterial() const { return material; }
|
||||
size_t GetHash() const {
|
||||
if (!hash.has_value()) hash = hasher(material);
|
||||
return hash.value();
|
||||
}
|
||||
|
||||
private:
|
||||
O::CKMaterial* material;
|
||||
CKMaterialHash hasher;
|
||||
mutable std::optional<size_t> hash;
|
||||
};
|
||||
|
||||
class CKMeshWrapper {
|
||||
public:
|
||||
CKMeshWrapper(O::CKMesh* mesh) : mesh(mesh), hasher(), hash(std::nullopt) {}
|
||||
~CKMeshWrapper() {}
|
||||
YYCC_DEFAULT_COPY_MOVE(CKMeshWrapper)
|
||||
|
||||
public:
|
||||
O::CKMesh* GetMesh() const { return mesh; }
|
||||
size_t GetHash() const {
|
||||
if (!hash.has_value()) hash = hasher(mesh);
|
||||
return hash.value();
|
||||
}
|
||||
|
||||
private:
|
||||
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 { return tex.GetHash(); }
|
||||
};
|
||||
|
||||
struct CKMaterialWrapperHash {
|
||||
[[nodiscard]] size_t operator()(const CKMaterialWrapper& mtl) const noexcept { return mtl.GetHash(); }
|
||||
};
|
||||
|
||||
struct CKMeshWrapperHash {
|
||||
[[nodiscard]] size_t operator()(const CKMeshWrapper& mesh) const noexcept { return mesh.GetHash(); }
|
||||
};
|
||||
|
||||
struct CKTextureWrapperEqualTo {
|
||||
CKTextureEqualTo equal_to;
|
||||
[[nodiscard]] bool operator()(const CKTextureWrapper& lhs, const CKTextureWrapper& rhs) const {
|
||||
if (lhs.GetHash() != rhs.GetHash()) return false;
|
||||
return equal_to(lhs.GetTexture(), rhs.GetTexture());
|
||||
}
|
||||
};
|
||||
|
||||
struct CKMaterialWrapperEqualTo {
|
||||
CKMaterialEqualTo equal_to;
|
||||
[[nodiscard]] bool operator()(const CKMaterialWrapper& lhs, const CKMaterialWrapper& rhs) const {
|
||||
if (lhs.GetHash() != rhs.GetHash()) return false;
|
||||
return equal_to(lhs.GetMaterial(), rhs.GetMaterial());
|
||||
}
|
||||
};
|
||||
|
||||
struct CKMeshWrapperEqualTo {
|
||||
CKMeshEqualTo equal_to;
|
||||
[[nodiscard]] bool operator()(const CKMeshWrapper& lhs, const CKMeshWrapper& rhs) const {
|
||||
if (lhs.GetHash() != rhs.GetHash()) return false;
|
||||
return equal_to(lhs.GetMesh(), rhs.GetMesh());
|
||||
}
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
constexpr char8_t YYC3[] = u8"YYC3";
|
||||
|
||||
YYCRule3::YYCRule3() : IRule() {}
|
||||
|
||||
YYCRule3::~YYCRule3() {}
|
||||
|
||||
std::u8string_view YYCRule3::GetRuleName() const {
|
||||
return YYC3;
|
||||
}
|
||||
|
||||
void YYCRule3::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
// 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(
|
||||
YYC3,
|
||||
u8"Some textures are visually identical. Please consider merging them to reduce the final map size. These textures are: %s.",
|
||||
Shared::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(
|
||||
YYC3,
|
||||
u8"Some materials are visually identical. Please consider merging them to reduce the final map size. These materials are: %s.",
|
||||
Shared::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(
|
||||
YYC3,
|
||||
u8"Some meshes are visually identical. Please consider merging them to reduce the final map size. These meshes are: %s.",
|
||||
Shared::QuoteObjectNames(dup_meshes.begin(), dup_meshes.end()).c_str());
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region YYC Rule 4
|
||||
|
||||
constexpr char8_t YYC4[] = u8"YYC4";
|
||||
|
||||
YYCRule4::YYCRule4() : IRule() {}
|
||||
|
||||
YYCRule4::~YYCRule4() {}
|
||||
|
||||
std::u8string_view YYCRule4::GetRuleName() const {
|
||||
return 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::TextureNames::OPAQUE_TEXS) {
|
||||
opaque_texs.emplace(strop::to_lower(tex_name));
|
||||
}
|
||||
std::set<std::u8string> transparent_texs;
|
||||
for (const auto* tex_name : Shared::TextureNames::TRANSPARENT_TEXS) {
|
||||
transparent_texs.emplace(strop::to_lower(tex_name));
|
||||
}
|
||||
|
||||
// Check texture one by one
|
||||
for (auto& tex : level.GetTextures()) {
|
||||
auto tex_filename = tex->GetUnderlyingData().GetSlotFileName(0);
|
||||
if (tex_filename == nullptr) continue;
|
||||
|
||||
auto lower_tex_filename = strop::to_lower(tex_filename);
|
||||
if (opaque_texs.contains(lower_tex_filename)) {
|
||||
if (tex->GetVideoFormat() != V::VX_PIXELFORMAT::_16_ARGB1555) {
|
||||
reporter.FormatWarning(
|
||||
YYC4,
|
||||
u8"Texture %s is Ballance opaque texture. But its video format is not ARGB1555. This is mismatched with vanilla Ballance.",
|
||||
Shared::QuoteObjectName(tex).c_str());
|
||||
}
|
||||
} else if (transparent_texs.contains(lower_tex_filename)) {
|
||||
if (tex->GetVideoFormat() != V::VX_PIXELFORMAT::_32_ARGB8888) {
|
||||
reporter.FormatWarning(
|
||||
YYC4,
|
||||
u8"Texture %s is Ballance transparent texture. But its video format is not ARGB8888. This is mismatched with vanilla Ballance.",
|
||||
Shared::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(
|
||||
YYC4,
|
||||
u8"Texture %s is not Ballance texture. Its video format is ARGB8888. "
|
||||
"This may cause useless performance consumption if there is no transparent inside it. Please check whether this is essential.",
|
||||
Shared::QuoteObjectName(tex).c_str());
|
||||
break;
|
||||
default:
|
||||
reporter.FormatWarning(
|
||||
YYC4,
|
||||
u8"Texture %s is not Ballance texture. Its video format is not ARGB1555 or ARGB8888. "
|
||||
"This is mismatched with vanilla Ballance. Please set it to ARGB1555 for opaque texture, or ARGB8888 for transaprent texture, "
|
||||
"except special scenario.",
|
||||
Shared::QuoteObjectName(tex).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
@@ -1,221 +0,0 @@
|
||||
#include "ZZQRules.hpp"
|
||||
#include "Shared.hpp"
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
|
||||
#pragma region ZZQ Rule 1
|
||||
|
||||
constexpr char8_t ZZQ1[] = u8"ZZQ1";
|
||||
|
||||
ZZQRule1::ZZQRule1() : IRule() {}
|
||||
|
||||
ZZQRule1::~ZZQRule1() {}
|
||||
|
||||
std::u8string_view ZZQRule1::GetRuleName() const {
|
||||
return 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::FetchGroup(ctx, Shared::GroupNames::PHYS_FLOORSTOPPER);
|
||||
if (phys_floorstopper == nullptr) return;
|
||||
// We iterate all object grouped into it.
|
||||
auto group_3dobjects = Shared::Iter3dObjects(phys_floorstopper);
|
||||
|
||||
// Show the first object if it have.
|
||||
if (!group_3dobjects.empty()) {
|
||||
auto* first_3dobjects = group_3dobjects.front();
|
||||
reporter.FormatInfo(
|
||||
ZZQ1,
|
||||
u8R"(Object %s is the first object grouped into "Phys_FloorStopper". It is the only stopper which can make sound in game.)",
|
||||
Shared::QuoteObjectName(first_3dobjects).c_str());
|
||||
}
|
||||
|
||||
// Warning for other objects
|
||||
for (size_t i = 1; i < group_3dobjects.size(); ++i) {
|
||||
auto* other_3dobject = group_3dobjects[i];
|
||||
reporter.FormatWarning(
|
||||
ZZQ1,
|
||||
u8R"(Object %s is grouped into "Phys_FloorStopper" but it is not the only object. This will cause it can not make sound in game. Please confirm this is by your intention.)",
|
||||
Shared::QuoteObjectName(other_3dobject).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ZZQ Rule 2
|
||||
|
||||
constexpr char8_t ZZQ2[] = u8"ZZQ2";
|
||||
|
||||
ZZQRule2::ZZQRule2() : IRule() {}
|
||||
|
||||
ZZQRule2::~ZZQRule2() {}
|
||||
|
||||
std::u8string_view ZZQRule2::GetRuleName() const {
|
||||
return ZZQ2;
|
||||
}
|
||||
|
||||
void ZZQRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
Shared::SectorNameBuilder builder;
|
||||
|
||||
// Extract group objects info
|
||||
std::vector<std::set<O::CK3dObject*>> sector_objects;
|
||||
for (L::CKDWORD i = Shared::MIN_SECTOR; i <= Shared::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::FetchGroup(ctx, sector_name.c_str());
|
||||
if (sector == nullptr) break;
|
||||
|
||||
auto group_3dobjects = Shared::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::FetchGroup(ctx, sector_names.legacy_name.c_str());
|
||||
auto* intuitive_sector = Shared::FetchGroup(ctx, sector_names.intuitive_name.c_str());
|
||||
if (legacy_sector == nullptr && intuitive_sector == nullptr) break;
|
||||
|
||||
if (legacy_sector != nullptr) {
|
||||
auto group_3dobjects = Shared::Iter3dObjects(legacy_sector);
|
||||
for (auto* group_3dobject : group_3dobjects) {
|
||||
object_set.emplace(group_3dobject);
|
||||
}
|
||||
}
|
||||
if (intuitive_sector != nullptr) {
|
||||
auto group_3dobjects = Shared::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(ZZQ2,
|
||||
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::QuoteObjectNames(intersection.begin(), intersection.end()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ZZQ Rule 3
|
||||
|
||||
constexpr char8_t ZZQ3[] = u8"ZZQ3";
|
||||
|
||||
ZZQRule3::ZZQRule3() : IRule() {}
|
||||
|
||||
ZZQRule3::~ZZQRule3() {}
|
||||
|
||||
std::u8string_view ZZQRule3::GetRuleName() const {
|
||||
return ZZQ3;
|
||||
}
|
||||
|
||||
void ZZQRule3::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
Shared::SectorNameBuilder builder;
|
||||
|
||||
auto* level_start = Shared::FetchGroup(ctx, Shared::GroupNames::PS_LEVELSTART);
|
||||
if (level_start == nullptr) {
|
||||
reporter.WriteError(ZZQ3, u8R"(Incomplete level: can not find "PS_Levelstart" group.)");
|
||||
} else {
|
||||
switch (level_start->GetObjectCount()) {
|
||||
case 0:
|
||||
reporter.WriteError(ZZQ3, u8R"(Incomplete level: there is no object grouped into "PS_Levelstart" group.)");
|
||||
break;
|
||||
case 1:
|
||||
// OK. Do nothing.
|
||||
break;
|
||||
default:
|
||||
reporter.WriteError(ZZQ3, u8R"(Bad level: there are more than one objects grouped into "PS_Levelstart" group.)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto* level_end = Shared::FetchGroup(ctx, Shared::GroupNames::PE_LEVELENDE);
|
||||
if (level_end == nullptr) {
|
||||
reporter.WriteError(ZZQ3, u8R"(Incomplete level: can not find "PE_Levelende" group.)");
|
||||
} else {
|
||||
switch (level_end->GetObjectCount()) {
|
||||
case 0:
|
||||
reporter.WriteError(ZZQ3, u8R"(Incomplete level: there is no object grouped into "PE_Levelende" group.)");
|
||||
break;
|
||||
case 1:
|
||||
// OK. Do nothing.
|
||||
break;
|
||||
default:
|
||||
reporter.WriteError(ZZQ3, u8R"(Bad level: there are more than one objects grouped into "PE_Levelende" group.)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto* check_points = Shared::FetchGroup(ctx, Shared::GroupNames::PC_CHECKPOINTS);
|
||||
if (check_points == nullptr) {
|
||||
reporter
|
||||
.WriteWarning(ZZQ3,
|
||||
u8R"(Can not find "PC_Checkpoints" group. This will cause bad render of particle at the level start point.)");
|
||||
}
|
||||
|
||||
auto* reset_points = Shared::FetchGroup(ctx, Shared::GroupNames::PR_RESETPOINTS);
|
||||
if (reset_points == nullptr) {
|
||||
reporter.WriteError(ZZQ3, u8R"(Incomplete level: can not find "PR_Resetpoints" group.)");
|
||||
} else {
|
||||
if (reset_points->GetObjectCount() == 0) {
|
||||
reporter.WriteError(ZZQ3, u8R"(Incomplete level: there is no object grouped into "PC_Resetpoints" group.)");
|
||||
}
|
||||
}
|
||||
|
||||
auto sector1_name = builder.get_name(1);
|
||||
auto* sector1 = Shared::FetchGroup(ctx, sector1_name.c_str());
|
||||
if (sector1 == nullptr) {
|
||||
reporter.WriteError(ZZQ3, u8R"(Incomplete level: can not find "Sector_01" group.)");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
@@ -1,27 +1,24 @@
|
||||
#include "BBugRules.hpp"
|
||||
#include "Shared.hpp"
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region BBug Rule 1
|
||||
|
||||
constexpr char8_t BBUG1[] = u8"BBUG1";
|
||||
|
||||
BBugRule1::BBugRule1() : IRule() {}
|
||||
BBugRule1::BBugRule1() : Rule::IRule() {}
|
||||
|
||||
BBugRule1::~BBugRule1() {}
|
||||
|
||||
std::u8string_view BBugRule1::GetRuleName() const {
|
||||
return BBUG1;
|
||||
return u8"BBUG1";
|
||||
}
|
||||
|
||||
void BBugRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
if (!level.GetTargetLights().empty()) {
|
||||
reporter.WriteInfo(BBUG1, u8"Using light in map is not suggested.");
|
||||
reporter.WriteInfo(u8"Using light in map is not suggested.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,19 +26,17 @@ namespace BMapInspector::Rule {
|
||||
|
||||
#pragma region BBug Rule 2
|
||||
|
||||
constexpr char8_t BBUG2[] = u8"BBUG2";
|
||||
|
||||
BBugRule2::BBugRule2() : IRule() {}
|
||||
BBugRule2::BBugRule2() : Rule::IRule() {}
|
||||
|
||||
BBugRule2::~BBugRule2() {}
|
||||
|
||||
std::u8string_view BBugRule2::GetRuleName() const {
|
||||
return BBUG2;
|
||||
return u8"BBUG2";
|
||||
}
|
||||
|
||||
void BBugRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
if (!level.GetTargetCameras().empty()) {
|
||||
reporter.WriteInfo(BBUG2, u8"Using camera in map is not suggested.");
|
||||
reporter.WriteInfo(u8"Using camera in map is not suggested.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,14 +44,12 @@ namespace BMapInspector::Rule {
|
||||
|
||||
#pragma region BBug Rule 3
|
||||
|
||||
constexpr char8_t BBUG3[] = u8"BBUG3";
|
||||
|
||||
BBugRule3::BBugRule3() : IRule() {}
|
||||
BBugRule3::BBugRule3() : Rule::IRule() {}
|
||||
|
||||
BBugRule3::~BBugRule3() {}
|
||||
|
||||
std::u8string_view BBugRule3::GetRuleName() const {
|
||||
return BBUG3;
|
||||
return u8"BBUG3";
|
||||
}
|
||||
|
||||
void BBugRule3::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
@@ -68,4 +61,4 @@ namespace BMapInspector::Rule {
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
} // namespace BMapInspector::Ruleset
|
||||
@@ -1,14 +1,14 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief BBug Rule 1
|
||||
* @details
|
||||
* Using light in map is not suggested.
|
||||
*/
|
||||
class BBugRule1 : public IRule {
|
||||
class BBugRule1 : public Rule::IRule {
|
||||
public:
|
||||
BBugRule1();
|
||||
virtual ~BBugRule1();
|
||||
@@ -24,7 +24,7 @@ namespace BMapInspector::Rule {
|
||||
* @details
|
||||
* Using camera in map is not suggested.
|
||||
*/
|
||||
class BBugRule2 : public IRule {
|
||||
class BBugRule2 : public Rule::IRule {
|
||||
public:
|
||||
BBugRule2();
|
||||
virtual ~BBugRule2();
|
||||
@@ -40,7 +40,7 @@ namespace BMapInspector::Rule {
|
||||
* @details
|
||||
* Check whether the parameters of all materials is same with Ballance vanilla settings.
|
||||
*/
|
||||
class BBugRule3 : public IRule {
|
||||
class BBugRule3 : public Rule::IRule {
|
||||
public:
|
||||
BBugRule3();
|
||||
virtual ~BBugRule3();
|
||||
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
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief chirs241097 Rule 1
|
||||
@@ -9,7 +9,7 @@ namespace BMapInspector::Rule {
|
||||
* 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 IRule {
|
||||
class ChirsRule1 : public Rule::IRule {
|
||||
public:
|
||||
ChirsRule1();
|
||||
virtual ~ChirsRule1();
|
||||
@@ -1,25 +1,24 @@
|
||||
#include "GpRules.hpp"
|
||||
#include "Shared.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::Rule {
|
||||
|
||||
// Reference: https://tieba.baidu.com/p/3182981807
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region GP1 Rule
|
||||
|
||||
constexpr char8_t GP1[] = u8"GP1";
|
||||
// Reference: https://tieba.baidu.com/p/3182981807
|
||||
|
||||
GpRule1::GpRule1() : IRule() {}
|
||||
GpRule1::GpRule1() : Rule::IRule() {}
|
||||
|
||||
GpRule1::~GpRule1() {}
|
||||
|
||||
std::u8string_view GpRule1::GetRuleName() const {
|
||||
return GP1;
|
||||
return u8"GP1";
|
||||
}
|
||||
|
||||
void GpRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
@@ -32,19 +31,17 @@ namespace BMapInspector::Rule {
|
||||
|
||||
#pragma region GP2 Rule
|
||||
|
||||
constexpr char8_t GP2[] = u8"GP2";
|
||||
|
||||
GpRule2::GpRule2() : IRule() {}
|
||||
GpRule2::GpRule2() : Rule::IRule() {}
|
||||
|
||||
GpRule2::~GpRule2() {}
|
||||
|
||||
std::u8string_view GpRule2::GetRuleName() const {
|
||||
return GP2;
|
||||
return u8"GP2";
|
||||
}
|
||||
|
||||
void GpRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
Shared::SectorNameBuilder builder;
|
||||
Shared::Sector::SectorNameBuilder builder;
|
||||
|
||||
// We need collect all group names first,
|
||||
// becuase in following code we need frequent visit them
|
||||
@@ -58,13 +55,13 @@ namespace BMapInspector::Rule {
|
||||
|
||||
// Check the sector count of this game.
|
||||
L::CKDWORD sector_count;
|
||||
for (sector_count = Shared::MIN_SECTOR; sector_count <= Shared::MAX_SECTOR; ++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::MIN_SECTOR) {
|
||||
reporter.WriteError(GP2, u8R"(Can not find any reasonable sector group in your map.)");
|
||||
if (sector_count == Shared::Sector::MIN_SECTOR) {
|
||||
reporter.WriteError(u8"Can not find any reasonable sector group in your map.");
|
||||
return;
|
||||
} else {
|
||||
break;
|
||||
@@ -78,7 +75,9 @@ namespace BMapInspector::Rule {
|
||||
break;
|
||||
}
|
||||
if (has_legacy_sector && has_intuitive_sector) {
|
||||
reporter.FormatError(GP2, u8R"(Found "Sector_9" and "Sector_09" at the same map. This is not allowed.)");
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -88,48 +87,47 @@ namespace BMapInspector::Rule {
|
||||
--sector_count;
|
||||
|
||||
// Report sector count info.
|
||||
reporter.FormatInfo(GP2, u8"Your map has %" PRIuCKDWORD " sector(s).", sector_count);
|
||||
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(
|
||||
GP2,
|
||||
u8"Your map only have one sector. This is okey but not suggested because it will cause mosaic issue on the flames of checkpoint. Consider adding another sector to resolve this issue.");
|
||||
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(
|
||||
GP2,
|
||||
u8"You are creating a map with more than 8 sectors. This will cause vanilla Ballance freezed when loading it. Please make sure that all players of your map have properly set 999 sector loader up.");
|
||||
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.WriteWarning(
|
||||
GP2,
|
||||
u8R"(You are using intuitive sector name, "Sector_09", for sector 9. This is only accepted by new 999 sector loader.)");
|
||||
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::MAX_SECTOR; ++i) {
|
||||
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(GP2,
|
||||
u8R"(Found group "%s" unexpected. Please check whether sector groups are successive in your map.)",
|
||||
sector_name.c_str());
|
||||
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(
|
||||
GP2,
|
||||
u8R"(Found group "%s" or "%s" unexpected. Please check whether sector groups are successive in your map.)",
|
||||
sector_names.legacy_name.c_str(),
|
||||
sector_names.intuitive_name.c_str());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,4 +135,4 @@ namespace BMapInspector::Rule {
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
} // namespace BMapInspector::Ruleset
|
||||
@@ -1,14 +1,14 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief Gamepiaynmo Rule 1
|
||||
* @details
|
||||
* The most comprehensive group checker inspired from Ballance Blender Plugin.
|
||||
*/
|
||||
class GpRule1 : public IRule {
|
||||
class GpRule1 : public Rule::IRule {
|
||||
public:
|
||||
GpRule1();
|
||||
virtual ~GpRule1();
|
||||
@@ -30,7 +30,7 @@ namespace BMapInspector::Rule {
|
||||
* \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 IRule {
|
||||
class GpRule2 : public Rule::IRule {
|
||||
public:
|
||||
GpRule2();
|
||||
virtual ~GpRule2();
|
||||
@@ -1,23 +1,22 @@
|
||||
#include "LXRules.hpp"
|
||||
#include "Shared.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::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region LX Rule 1
|
||||
|
||||
constexpr char8_t LX1[] = u8"LX1";
|
||||
|
||||
LXRule1::LXRule1() : IRule() {}
|
||||
LXRule1::LXRule1() : Rule::IRule() {}
|
||||
|
||||
LXRule1::~LXRule1() {}
|
||||
|
||||
std::u8string_view LXRule1::GetRuleName() const {
|
||||
return LX1;
|
||||
return u8"LX1";
|
||||
}
|
||||
|
||||
void LXRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
@@ -25,10 +24,10 @@ namespace BMapInspector::Rule {
|
||||
|
||||
// First we fetch all Ballance element and push them into set.
|
||||
std::set<O::CK3dObject*> elements;
|
||||
for (auto* group_name : Shared::GroupNames::ALL_PH) {
|
||||
auto* group = Shared::FetchGroup(ctx, group_name);
|
||||
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::Iter3dObjects(group);
|
||||
auto group_objects = Shared::Utility::Iter3dObjects(group);
|
||||
for (auto* group_object : group_objects) {
|
||||
elements.emplace(group_object);
|
||||
}
|
||||
@@ -49,7 +48,7 @@ namespace BMapInspector::Rule {
|
||||
// because we do not want to duplicatedly process it.
|
||||
if (mesh_insert_rv.second) {
|
||||
// Iterate all meshes
|
||||
auto mtls = Shared::IterMaterial(mesh);
|
||||
auto mtls = Shared::Utility::IterMaterial(mesh);
|
||||
for (auto* mtl : mtls) {
|
||||
// Add into material set
|
||||
auto mtl_insert_rv = element_materials.emplace(mtl);
|
||||
@@ -76,23 +75,21 @@ namespace BMapInspector::Rule {
|
||||
if (mesh == nullptr) continue;
|
||||
// And check mesh
|
||||
if (element_meshes.contains(mesh)) {
|
||||
reporter.FormatError(
|
||||
LX1,
|
||||
u8R"(Object %s used mesh %s is already used by a Ballance element. This will cause this object can not be rendered correctly in level.)",
|
||||
Shared::QuoteObjectName(other_object).c_str(),
|
||||
Shared::QuoteObjectName(mesh).c_str());
|
||||
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::IterMaterial(mesh);
|
||||
auto mtls = Shared::Utility::IterMaterial(mesh);
|
||||
for (auto* mtl : mtls) {
|
||||
if (element_materials.contains(mtl)) {
|
||||
reporter.FormatError(
|
||||
LX1,
|
||||
u8R"(Object %s used material %s (referred by mesh %s) is already used by a Ballance element. This will cause this object can not be rendered correctly in level.)",
|
||||
Shared::QuoteObjectName(other_object).c_str(),
|
||||
Shared::QuoteObjectName(mtl).c_str(),
|
||||
Shared::QuoteObjectName(mesh).c_str());
|
||||
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
|
||||
@@ -101,12 +98,12 @@ namespace BMapInspector::Rule {
|
||||
// And check it
|
||||
if (element_textures.contains(texture)) {
|
||||
reporter.FormatError(
|
||||
LX1,
|
||||
u8R"(Object %s used texture %s (referred by mesh "%s" and material "%s") is already used by a Ballance element. This will cause this object can not be rendered correctly in level.)",
|
||||
Shared::QuoteObjectName(other_object).c_str(),
|
||||
Shared::QuoteObjectName(texture).c_str(),
|
||||
Shared::QuoteObjectName(mesh).c_str(),
|
||||
Shared::QuoteObjectName(mtl).c_str());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,4 +113,4 @@ namespace BMapInspector::Rule {
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
} // namespace BMapInspector::Ruleset
|
||||
@@ -1,14 +1,14 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
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 IRule {
|
||||
class LXRule1 : public Rule::IRule {
|
||||
public:
|
||||
LXRule1();
|
||||
virtual ~LXRule1();
|
||||
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
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief SomeOne_001 Rule 1
|
||||
@@ -9,7 +9,7 @@ namespace BMapInspector::Rule {
|
||||
* If there is a physicalized object without any mesh,
|
||||
* itself and following objects will not be physicalized.
|
||||
*/
|
||||
class SOneRule1 : public IRule {
|
||||
class SOneRule1 : public Rule::IRule {
|
||||
public:
|
||||
SOneRule1();
|
||||
virtual ~SOneRule1();
|
||||
@@ -20,4 +20,4 @@ namespace BMapInspector::Rule {
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
} // namespace BMapInspector::Ruleset
|
||||
@@ -1,28 +1,28 @@
|
||||
#include "SSBRules.hpp"
|
||||
#include "Shared.hpp"
|
||||
#include "Shared/Utility.hpp"
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace V = LibCmo::VxMath;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
#pragma region SSB Rule 1
|
||||
|
||||
constexpr char8_t SSB1[] = u8"SSB1";
|
||||
constexpr L::CKFLOAT TOLERANCE = 0.001f;
|
||||
|
||||
SSBRule1::SSBRule1() : IRule() {}
|
||||
SSBRule1::SSBRule1() : Rule::IRule() {}
|
||||
|
||||
SSBRule1::~SSBRule1() {}
|
||||
|
||||
std::u8string_view SSBRule1::GetRuleName() const {
|
||||
return SSB1;
|
||||
return u8"SSB1";
|
||||
}
|
||||
|
||||
void SSBRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
|
||||
auto* ctx = level.GetCKContext();
|
||||
auto physicalized_3dobjects = Shared::FetchPhysicalized3dObjects(ctx);
|
||||
auto physicalized_3dobjects = Shared::Utility::FetchPhysicalized3dObjects(ctx);
|
||||
|
||||
// Iterate all physicalized 3dobject
|
||||
for (auto* physicalized_3dobject : physicalized_3dobjects) {
|
||||
@@ -41,19 +41,18 @@ namespace BMapInspector::Rule {
|
||||
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::FPEqual(col1.Length(), 1.0f, TOLERANCE)) has_scale = true;
|
||||
if (!Shared::FPEqual(col2.Length(), 1.0f, TOLERANCE)) has_scale = true;
|
||||
if (!Shared::FPEqual(col3.Length(), 1.0f, TOLERANCE)) has_scale = true;
|
||||
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(
|
||||
SSB1,
|
||||
u8R"(Object %s grouped into physicalization groups has scale factor. This will cause its collision shape is different with its render shape.)",
|
||||
Shared::QuoteObjectName(physicalized_3dobject).c_str());
|
||||
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::Rule
|
||||
} // namespace BMapInspector::Ruleset
|
||||
@@ -1,14 +1,14 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief speedystoneball Rule 1
|
||||
* @details
|
||||
* Physicalized object should not have scale factor, especially negative scale factor (mirror).
|
||||
*/
|
||||
class SSBRule1 : public IRule {
|
||||
class SSBRule1 : public Rule::IRule {
|
||||
public:
|
||||
SSBRule1();
|
||||
virtual ~SSBRule1();
|
||||
@@ -19,4 +19,4 @@ namespace BMapInspector::Rule {
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
} // namespace BMapInspector::Rule
|
||||
} // 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
@@ -1,20 +1,9 @@
|
||||
#pragma once
|
||||
#include <VTAll.hpp>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/op.hpp>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
namespace BMapInspector::Rule::Shared {
|
||||
namespace BMapInspector::Ruleset::Shared::Name {
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
#pragma region Constant Values
|
||||
|
||||
namespace GroupNames {
|
||||
namespace Group {
|
||||
// clang-format off
|
||||
constexpr char8_t PS_LEVELSTART[] = u8"PS_Levelstart";
|
||||
constexpr char8_t PE_LEVELENDE[] = u8"PE_Levelende";
|
||||
@@ -56,14 +45,13 @@ namespace BMapInspector::Rule::Shared {
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
} // namespace GroupNames
|
||||
} // namespace Group
|
||||
|
||||
namespace TextureNames {
|
||||
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",
|
||||
@@ -234,123 +222,6 @@ namespace BMapInspector::Rule::Shared {
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
} // namespace TextureNames
|
||||
} // namespace Texture
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Utility Classes
|
||||
|
||||
constexpr L::CKDWORD MIN_SECTOR = 1;
|
||||
constexpr L::CKDWORD MAX_SECTOR = 999;
|
||||
|
||||
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
|
||||
* @param[in] sector
|
||||
* @return
|
||||
* @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(L::CKDWORD sector) const;
|
||||
Sector9Names get_sector9_names() const;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Check Functions
|
||||
|
||||
/**
|
||||
* @brief Check whether given 2 float point values are equal with given tolerance.
|
||||
* @param lhs
|
||||
* @param rhs
|
||||
* @param tolerance
|
||||
* @return
|
||||
*/
|
||||
bool FPEqual(L::CKFLOAT lhs, L::CKFLOAT rhs, L::CKFLOAT tolerance);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* @param[in] ctx Can not be nullptr.
|
||||
* @param[in] name Can not be nullptr.
|
||||
* @return Found pointer to CKGroup, otherwise nullptr.
|
||||
*/
|
||||
O::CKGroup* FetchGroup(C::CKContext* ctx, L::CKSTRING name);
|
||||
/**
|
||||
* @brief
|
||||
* @param[in] ctx Can not be nullptr.
|
||||
* @param[in] name Can not be nullptr.
|
||||
* @return Found pointer to CKMaterial, otherwise nullptr.
|
||||
*/
|
||||
O::CKMaterial* FetchMaterial(C::CKContext* ctx, L::CKSTRING name);
|
||||
std::vector<O::CK3dObject*> FetchPhysicalized3dObjects(C::CKContext* ctx);
|
||||
/**
|
||||
* @brief Check whether given CKTexture has the given file name (case-insensitive).
|
||||
* @param[in] tex Can not be nullptr.
|
||||
* @param[in] name Can not be nullptr.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool CheckTextureFileName(O::CKTexture* tex, L::CKSTRING name);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* @param[in] group Can not be nullptr.
|
||||
* @return All objects is the child class of CK3dEntity.
|
||||
*/
|
||||
std::vector<O::CK3dObject*> Iter3dObjects(O::CKGroup* group);
|
||||
/**
|
||||
* @brief
|
||||
* @param[in] mesh Can not be nullptr.
|
||||
* @return All nullptr reference are removed.
|
||||
*/
|
||||
std::vector<O::CKMaterial*> IterMaterial(O::CKMesh* mesh);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* @param[in] obj Can not be nullptr.
|
||||
* @return
|
||||
*/
|
||||
std::u8string QuoteObjectName(O::CKObject* obj);
|
||||
/**
|
||||
* @brief
|
||||
* @tparam InputIt
|
||||
* @param first
|
||||
* @param last
|
||||
* @return
|
||||
*/
|
||||
template<std::input_iterator InputIt>
|
||||
requires std::is_pointer_v<std::iter_value_t<InputIt>>
|
||||
&& std::is_base_of_v<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::Rule::Shared
|
||||
} // 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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "Shared.hpp"
|
||||
#include "Utility.hpp"
|
||||
#include "Name.hpp"
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/carton/termcolor.hpp>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
@@ -6,38 +8,14 @@
|
||||
|
||||
namespace strop = yycc::string::op;
|
||||
namespace termcolor = yycc::carton::termcolor;
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
namespace O = LibCmo::CK2::ObjImpls;
|
||||
|
||||
namespace BMapInspector::Rule::Shared {
|
||||
namespace BMapInspector::Ruleset::Shared::Utility {
|
||||
|
||||
#pragma region Utility Classes
|
||||
#pragma region Utilities
|
||||
|
||||
#pragma region Sector Name Builder
|
||||
|
||||
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"};
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Check Functions
|
||||
|
||||
bool FPEqual(L::CKFLOAT lhs, L::CKFLOAT rhs, L::CKFLOAT tolerance) {
|
||||
auto diff = lhs - rhs;
|
||||
@@ -45,6 +23,10 @@ namespace BMapInspector::Rule::Shared {
|
||||
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));
|
||||
}
|
||||
@@ -65,25 +47,32 @@ namespace BMapInspector::Rule::Shared {
|
||||
std::vector<O::CK3dObject*> FetchPhysicalized3dObjects(C::CKContext* ctx) {
|
||||
std::vector<O::CK3dObject*> rv;
|
||||
|
||||
auto* phys_floors = FetchGroup(ctx, GroupNames::PHYS_FLOORS);
|
||||
auto* phys_floors = FetchGroup(ctx, Name::Group::PHYS_FLOORS);
|
||||
if (phys_floors != nullptr) Iter3dObjectsEx(rv, phys_floors);
|
||||
auto* phys_floorrails = FetchGroup(ctx, GroupNames::PHYS_FLOORRAILS);
|
||||
auto* phys_floorrails = FetchGroup(ctx, Name::Group::PHYS_FLOORRAILS);
|
||||
if (phys_floorrails != nullptr) Iter3dObjectsEx(rv, phys_floorrails);
|
||||
auto* phys_floorstopper = FetchGroup(ctx, GroupNames::PHYS_FLOORSTOPPER);
|
||||
auto* phys_floorstopper = FetchGroup(ctx, Name::Group::PHYS_FLOORSTOPPER);
|
||||
if (phys_floorstopper != nullptr) Iter3dObjectsEx(rv, phys_floorstopper);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool CheckTextureFileName(O::CKTexture* tex, L::CKSTRING name) {
|
||||
std::optional<std::u8string> ExtractTextureFileName(O::CKTexture* tex) {
|
||||
// Get file name
|
||||
auto filename = tex->GetUnderlyingData().GetSlotFileName(0);
|
||||
if (filename == nullptr) return false;
|
||||
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.c_str(), name);
|
||||
return C::CKStrEqualI(filename_part.value().c_str(), name);
|
||||
}
|
||||
|
||||
std::vector<O::CK3dObject*> Iter3dObjects(O::CKGroup* group) {
|
||||
@@ -103,24 +92,31 @@ namespace BMapInspector::Rule::Shared {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static const char8_t* RenderObjectName(O::CKObject* obj) {
|
||||
static std::u8string ANONYMOUS = termcolor::colored(u8"<anonymous>", termcolor::Color::LightMagenta);
|
||||
auto name = obj->GetName();
|
||||
if (name == nullptr) {
|
||||
return ANONYMOUS.c_str();
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#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) {
|
||||
std::u8string rv;
|
||||
rv.push_back(u8'"');
|
||||
rv.append(RenderObjectName(obj));
|
||||
rv.push_back(u8'"');
|
||||
return rv;
|
||||
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::Rule::Shared
|
||||
} // 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
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief YYC12345 Rule 1
|
||||
@@ -9,7 +9,7 @@ namespace BMapInspector::Rule {
|
||||
* The object grouped into "Phys_FloorRails" should only be rails, otherwise their meshes' UV will be smooth.
|
||||
* Additionally, these smooth UV meshes will also affect those objects refering them.
|
||||
*/
|
||||
class YYCRule1 : public IRule {
|
||||
class YYCRule1 : public Rule::IRule {
|
||||
public:
|
||||
YYCRule1();
|
||||
virtual ~YYCRule1();
|
||||
@@ -26,7 +26,7 @@ namespace BMapInspector::Rule {
|
||||
* The object grouped into physicalization group should not have isolated vertex,
|
||||
* otherwise it will fail to be physicalized.
|
||||
*/
|
||||
class YYCRule2 : public IRule {
|
||||
class YYCRule2 : public Rule::IRule {
|
||||
public:
|
||||
YYCRule2();
|
||||
virtual ~YYCRule2();
|
||||
@@ -42,7 +42,7 @@ namespace BMapInspector::Rule {
|
||||
* @details
|
||||
* Exactly same mesh, material and texture can be merged.
|
||||
*/
|
||||
class YYCRule3 : public IRule {
|
||||
class YYCRule3 : public Rule::IRule {
|
||||
public:
|
||||
YYCRule3();
|
||||
virtual ~YYCRule3();
|
||||
@@ -60,7 +60,7 @@ namespace BMapInspector::Rule {
|
||||
* \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 IRule {
|
||||
class YYCRule4 : public Rule::IRule {
|
||||
public:
|
||||
YYCRule4();
|
||||
virtual ~YYCRule4();
|
||||
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
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "../Rule.hpp"
|
||||
|
||||
namespace BMapInspector::Rule {
|
||||
namespace BMapInspector::Ruleset {
|
||||
|
||||
/**
|
||||
* @brief ZZQ Rule 1
|
||||
@@ -13,7 +13,7 @@ namespace BMapInspector::Rule {
|
||||
* 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 IRule {
|
||||
class ZZQRule1 : public Rule::IRule {
|
||||
public:
|
||||
ZZQRule1();
|
||||
virtual ~ZZQRule1();
|
||||
@@ -30,7 +30,7 @@ namespace BMapInspector::Rule {
|
||||
* The Ballance should only be included only one group.
|
||||
* This rule will check whether there is intersection between different sector group.
|
||||
*/
|
||||
class ZZQRule2 : public IRule {
|
||||
class ZZQRule2 : public Rule::IRule {
|
||||
public:
|
||||
ZZQRule2();
|
||||
virtual ~ZZQRule2();
|
||||
@@ -50,7 +50,7 @@ namespace BMapInspector::Rule {
|
||||
* \li One reset point.
|
||||
* \li "Sector_01" group.
|
||||
*/
|
||||
class ZZQRule3 : public IRule {
|
||||
class ZZQRule3 : public Rule::IRule {
|
||||
public:
|
||||
ZZQRule3();
|
||||
virtual ~ZZQRule3();
|
||||
@@ -16,6 +16,11 @@ namespace BMapInspector::Utils {
|
||||
Info = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Parse given string as report level.
|
||||
* @param[in] value The string for parsing.
|
||||
* @return Parsed level or nothing (error occurs).
|
||||
*/
|
||||
std::optional<ReportLevel> ParseReportLevel(const std::u8string_view& value);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user