1
0

12 Commits

48 changed files with 2304 additions and 1572 deletions

View File

@@ -9,6 +9,19 @@ MKDIR install
:: Build with x64 architecture in Release mode
CD build
:: We set this to revert the incompatible ABI for MSVC STL.
:: See: https://github.com/microsoft/STL/wiki/VS-2022-Changelog#vs-2022-1710
::
:: Ideally, I can install new VCRedist to resolve this issue.
:: However, Blender embeds its own VCRedist when distribution which has lower VCRedist version.
:: And at the same time, Blender will load our BMap.dll built by new VCRedist, so it trigger the incompatible ABI issue.
::
:: Currently, the VCRedist distributed by Blender 4.5 LTS is incompatible with our BMap.dll.
:: So I write it in script, rather than in CMake file, to temporaryly fix this issue.
::
:: This fix also is written in dependency build scripts, please remove them together if you remove this,
:: when Blender migrate to the new VCRedist.
set CXXFLAGS=/D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR=1
cmake -A x64 -DCMAKE_CXX_STANDARD=23 -DNEMO_BUILD_UNVIRT=ON -DNEMO_BUILD_BALLANCE=ON -DNEMO_BUILD_BMAP=ON -DNEMO_BUILD_BMAPINSPECTOR=ON -DYYCCommonplace_ROOT=%YYCCommonplace_ROOT% -DSTB_ROOT=%STB_ROOT% -DZLIB_ROOT=%ZLIB_ROOT% ../..
cmake --build . --config Release
cmake --install . --prefix=../install --config Release

View File

@@ -9,6 +9,7 @@ MKDIR install
:: Build with x64 architecture in Release mode
CD build
set CXXFLAGS=/D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR=1
cmake -A x64 -DCMAKE_CXX_STANDARD=23 ../..
cmake --build . --config Release
cmake --install . --prefix=../install --config Release

View File

@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: 'yyc12345/YYCCommonplace'
ref: 'master'
ref: 'v2.0.0'
path: 'extern/YYCCommonplace'
- name: Build YYCCommonplace
shell: bash

View File

@@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: 'yyc12345/YYCCommonplace'
ref: 'master'
ref: 'v2.0.0'
path: 'extern/YYCCommonplace'
- name: Build YYCCommonplace
shell: bash

View File

@@ -20,7 +20,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: 'yyc12345/YYCCommonplace'
ref: 'master'
ref: 'v2.0.0'
path: 'extern/YYCCommonplace'
- name: Build YYCCommonplace
shell: cmd

View File

@@ -5,10 +5,11 @@
#include "Rule.hpp"
#include <VTAll.hpp>
#include <yycc.hpp>
#include <yycc/carton/termcolor.hpp>
#include <yycc/string/op.hpp>
#include <yycc/patch/stream.hpp>
#include <yycc/windows/console.hpp>
#include <yycc/carton/termcolor.hpp>
#include <yycc/carton/ironpad.hpp>
#include <iostream>
#include <optional>
@@ -101,14 +102,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

View File

@@ -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

View File

@@ -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) {
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::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::AddReport(ReportLevel level, const std::u8string_view &content) {
if (this->current_rule.has_value()) {
this->reports.emplace_back(Report{
.level = level,
.rule = std::u8string(rule),
.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 &rule, const std::u8string_view &content) {
this->AddReport(Utils::ReportLevel::Info, rule, content);
void Reporter::WriteInfo(const std::u8string_view &content) {
this->AddReport(ReportLevel::Info, content);
}
void Reporter::FormatInfo(const std::u8string_view &rule, const char8_t *fmt, ...) {
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;
}

View File

@@ -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;
};

View File

@@ -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,41 +19,46 @@ 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 BBugRule1());
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::YYCRule5());
rules.emplace_back(new Ruleset::YYCRule6());
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::Rule

View File

@@ -10,6 +10,9 @@
namespace BMapInspector::Rule {
/**
* @brief The interface of a rule.
*/
class IRule {
public:
IRule();
@@ -21,11 +24,14 @@ 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;
@@ -35,4 +41,4 @@ namespace BMapInspector::Rule {
std::vector<IRule*> rules;
};
} // namespace BMapInspector::Ruleset
} // namespace BMapInspector::Rule

View File

@@ -1,30 +0,0 @@
#include "BBugRules.hpp"
#include "Shared.hpp"
namespace L = LibCmo;
namespace C = LibCmo::CK2;
namespace O = LibCmo::CK2::ObjImpls;
namespace BMapInspector::Rule {
#pragma region BBug Rule 1
constexpr char8_t BBUG1[] = u8"BBUG1";
BBugRule1::BBugRule1() : IRule() {}
BBugRule1::~BBugRule1() {}
std::u8string_view BBugRule1::GetRuleName() const {
return 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.");
}
}
#pragma endregion
} // namespace BMapInspector::Rule

View File

@@ -1,22 +0,0 @@
#pragma once
#include "../Rule.hpp"
namespace BMapInspector::Rule {
/**
* @brief BBug Rule 1
* @details
* Using light in map is not suggested.
*/
class BBugRule1 : public IRule {
public:
BBugRule1();
virtual ~BBugRule1();
YYCC_DELETE_COPY_MOVE(BBugRule1)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
}

View File

@@ -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

View File

@@ -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

View File

@@ -1,186 +0,0 @@
#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 L = LibCmo;
namespace C = LibCmo::CK2;
namespace O = LibCmo::CK2::ObjImpls;
#pragma region Constant Values
namespace GroupNames {
// clang-format off
constexpr char8_t PS_LEVELSTART[] = u8"PS_Levelstart";
constexpr char8_t PE_LEVELENDE[] = u8"PE_Levelende";
constexpr char8_t PC_CHECKPOINTS[] = u8"PC_Checkpoints";
constexpr char8_t PR_RESETPOINTS[] = u8"PR_Resetpoints";
constexpr char8_t PHYS_FLOORS[] = u8"Phys_Floors";
constexpr char8_t PHYS_FLOORRAILS[] = u8"Phys_FloorRails";
constexpr char8_t PHYS_FLOORSTOPPER[] = u8"Phys_FloorStopper";
constexpr std::array ALL_PH{
u8"P_Extra_Life",
u8"P_Extra_Point",
u8"P_Trafo_Paper",
u8"P_Trafo_Stone",
u8"P_Trafo_Wood",
u8"P_Ball_Paper",
u8"P_Ball_Stone",
u8"P_Ball_Wood",
u8"P_Box",
u8"P_Dome",
u8"P_Modul_01",
u8"P_Modul_03",
u8"P_Modul_08",
u8"P_Modul_17",
u8"P_Modul_18",
u8"P_Modul_19",
u8"P_Modul_25",
u8"P_Modul_26",
u8"P_Modul_29",
u8"P_Modul_30",
u8"P_Modul_34",
u8"P_Modul_37",
u8"P_Modul_41",
u8"PS_Levelstart",
u8"PE_Levelende",
u8"PC_Checkpoints",
u8"PR_Resetpoints",
};
// clang-format on
} // namespace GroupNames
namespace TextureNames {
// clang-format off
constexpr char8_t RAIL_ENVIRONMENT[] = u8"Rail_Environment.bmp";
constexpr char8_t LATERNE_VERLAUF[] = u8"Laterne_Verlauf.tga";
// clang-format on
} // namespace TextureNames
#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

View File

@@ -1,631 +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 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 = yycc::string::op::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
} // namespace BMapInspector::Rule

View File

@@ -1,56 +0,0 @@
#pragma once
#include "../Rule.hpp"
namespace BMapInspector::Rule {
/**
* @brief YYC12345 Rule 1
* @details
* The object grouped into "Phys_FloorRails" should only be rails, otherwise their meshes' UV will be smooth.
* Additionally, these smooth UV meshes will also affect those objects refering them.
*/
class YYCRule1 : public IRule {
public:
YYCRule1();
virtual ~YYCRule1();
YYCC_DELETE_COPY_MOVE(YYCRule1)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief YYC12345 Rule 2
* @details
* The object grouped into physicalization group should not have isolated vertex,
* otherwise it will fail to be physicalized.
*/
class YYCRule2 : public IRule {
public:
YYCRule2();
virtual ~YYCRule2();
YYCC_DELETE_COPY_MOVE(YYCRule2)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief YYC12345 Rule 3
* @details
* Exactly same mesh, material and texture can be merged.
*/
class YYCRule3 : public IRule {
public:
YYCRule3();
virtual ~YYCRule3();
YYCC_DELETE_COPY_MOVE(YYCRule3)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
}

View File

@@ -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

View File

@@ -0,0 +1,64 @@
#include "BBugRules.hpp"
namespace L = LibCmo;
namespace C = LibCmo::CK2;
namespace O = LibCmo::CK2::ObjImpls;
namespace BMapInspector::Ruleset {
#pragma region BBug Rule 1
BBugRule1::BBugRule1() : Rule::IRule() {}
BBugRule1::~BBugRule1() {}
std::u8string_view BBugRule1::GetRuleName() const {
return u8"BBUG1";
}
void BBugRule1::Check(Reporter::Reporter& reporter, Map::Level& level) const {
if (!level.GetTargetLights().empty()) {
reporter.WriteInfo(u8"Using light in map is not suggested.");
}
}
#pragma endregion
#pragma region BBug Rule 2
BBugRule2::BBugRule2() : Rule::IRule() {}
BBugRule2::~BBugRule2() {}
std::u8string_view BBugRule2::GetRuleName() const {
return u8"BBUG2";
}
void BBugRule2::Check(Reporter::Reporter& reporter, Map::Level& level) const {
if (!level.GetTargetCameras().empty()) {
reporter.WriteInfo(u8"Using camera in map is not suggested.");
}
}
#pragma endregion
#pragma region BBug Rule 3
BBugRule3::BBugRule3() : Rule::IRule() {}
BBugRule3::~BBugRule3() {}
std::u8string_view BBugRule3::GetRuleName() const {
return u8"BBUG3";
}
void BBugRule3::Check(Reporter::Reporter& reporter, Map::Level& level) const {
// TODO:
// This rule is complex and can be done by Ballance Blender Plugin.
// So we are not urgently to implement it in there.
// Just make a rule placeholder in there and may finish it in future.
}
#pragma endregion
} // namespace BMapInspector::Ruleset

View File

@@ -0,0 +1,54 @@
#pragma once
#include "../Rule.hpp"
namespace BMapInspector::Ruleset {
/**
* @brief BBug Rule 1
* @details
* Using light in map is not suggested.
*/
class BBugRule1 : public Rule::IRule {
public:
BBugRule1();
virtual ~BBugRule1();
YYCC_DELETE_COPY_MOVE(BBugRule1)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief BBug Rule 2
* @details
* Using camera in map is not suggested.
*/
class BBugRule2 : public Rule::IRule {
public:
BBugRule2();
virtual ~BBugRule2();
YYCC_DELETE_COPY_MOVE(BBugRule2)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief BBug Rule 3
* @details
* Check whether the parameters of all materials is same with Ballance vanilla settings.
*/
class BBugRule3 : public Rule::IRule {
public:
BBugRule3();
virtual ~BBugRule3();
YYCC_DELETE_COPY_MOVE(BBugRule3)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
}

View 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

View File

@@ -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();

View File

@@ -1,50 +1,48 @@
#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 {
// TODO:
// Finish this rule.
// It is so complex that I don't want to implement it now.
// This rule is complex and can be done by Ballance Blender Plugin.
// So we are not urgently to implement it in there.
// Just make a rule placeholder in there and may finish it in future.
}
#pragma endregion
#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 +56,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 +76,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 +88,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 +136,4 @@ namespace BMapInspector::Rule {
#pragma endregion
} // namespace BMapInspector::Rule
} // namespace BMapInspector::Ruleset

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
#include "Name.hpp"
namespace BMapInspector::Ruleset::Shared::Name {}

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -0,0 +1,374 @@
#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 <algorithm>
#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.FormatInfo(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.FormatInfo(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.FormatInfo(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.FormatInfo(
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;
}
}
}
}
#pragma endregion
#pragma region YYC Rule 5
YYCRule5::YYCRule5() : Rule::IRule() {}
YYCRule5::~YYCRule5() {}
std::u8string_view YYCRule5::GetRuleName() const {
return u8"YYC5";
}
void YYCRule5::Check(Reporter::Reporter& reporter, Map::Level& level) const {
// Build lowercase texture name set first.
std::set<std::u8string> texs;
for (const auto* tex_name : Shared::Name::Texture::ALL) {
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());
bool is_ballance_tex = texs.contains(lower_tex_filename);
using C::CK_TEXTURE_SAVEOPTIONS;
switch (tex->GetUnderlyingData().GetSaveOptions()) {
case CK_TEXTURE_SAVEOPTIONS::CKTEXTURE_USEGLOBAL:
reporter.FormatInfo(u8"The save option of texture %s rely on global Virtools settings. "
u8"This cause ambiguity and different behavior on different Virtools by different user settings. "
u8"Please consider change it to explicit option, such as External or Raw Data.",
Shared::Utility::QuoteObjectName(tex).c_str());
break;
case CK_TEXTURE_SAVEOPTIONS::CKTEXTURE_EXTERNAL:
if (!is_ballance_tex) {
reporter.FormatWarning(u8"Texture %s is not Ballance texture, but its save option is External. "
u8"This may cause texture loss when rendering in game. Please consider store it inside map.",
Shared::Utility::QuoteObjectName(tex).c_str());
}
break;
default:
if (is_ballance_tex) {
reporter.FormatInfo(u8"Texture %s is Ballance texture, but its save option is not External. "
u8"Please consider storing it as External to reduce the final size of map file, "
u8"and let user specified texture pack work.",
Shared::Utility::QuoteObjectName(tex).c_str());
}
break;
}
}
}
#pragma endregion
#pragma region YYC Rule 6
YYCRule6::YYCRule6() : Rule::IRule() {}
YYCRule6::~YYCRule6() {}
std::u8string_view YYCRule6::GetRuleName() const {
return u8"YYC6";
}
void YYCRule6::Check(Reporter::Reporter& reporter, Map::Level& level) const {
// TODO:
// This rule is not so essential.
// So we are not urgently to implement it in there.
// Just make a rule placeholder in there and may finish it in future.
}
#pragma endregion
} // namespace BMapInspector::Ruleset

View File

@@ -0,0 +1,106 @@
#pragma once
#include "../Rule.hpp"
namespace BMapInspector::Ruleset {
/**
* @brief YYC12345 Rule 1
* @details
* The object grouped into "Phys_FloorRails" should only be rails, otherwise their meshes' UV will be smooth.
* Additionally, these smooth UV meshes will also affect those objects refering them.
*/
class YYCRule1 : public Rule::IRule {
public:
YYCRule1();
virtual ~YYCRule1();
YYCC_DELETE_COPY_MOVE(YYCRule1)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief YYC12345 Rule 2
* @details
* The object grouped into physicalization group should not have isolated vertex,
* otherwise it will fail to be physicalized.
*/
class YYCRule2 : public Rule::IRule {
public:
YYCRule2();
virtual ~YYCRule2();
YYCC_DELETE_COPY_MOVE(YYCRule2)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief YYC12345 Rule 3
* @details
* Exactly same mesh, material and texture can be merged.
*/
class YYCRule3 : public Rule::IRule {
public:
YYCRule3();
virtual ~YYCRule3();
YYCC_DELETE_COPY_MOVE(YYCRule3)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief YYC12345 Rule 4
* @details
* Check the video format for Ballance and user-defined texture respectively.
* Report if there is non-vanilla Ballance settings.
*/
class YYCRule4 : public Rule::IRule {
public:
YYCRule4();
virtual ~YYCRule4();
YYCC_DELETE_COPY_MOVE(YYCRule4)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief YYC12345 Rule 5
* @details
* Check the save options for Ballance and user-defined texture respectively
* for reducing map size and avoid ambiguity.
*/
class YYCRule5 : public Rule::IRule {
public:
YYCRule5();
virtual ~YYCRule5();
YYCC_DELETE_COPY_MOVE(YYCRule5)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
/**
* @brief YYC12345 Rule 6
* @details
* Show info hint for the skip of progressbar when loading map.
*/
class YYCRule6 : public Rule::IRule {
public:
YYCRule6();
virtual ~YYCRule6();
YYCC_DELETE_COPY_MOVE(YYCRule6)
public:
std::u8string_view GetRuleName() const override;
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
};
}

View 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

View File

@@ -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,11 +30,11 @@ 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();
YYCC_DEFAULT_COPY_MOVE(ZZQRule2)
YYCC_DELETE_COPY_MOVE(ZZQRule2)
public:
std::u8string_view GetRuleName() const override;
@@ -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();

View File

@@ -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);
/**

View File

@@ -50,8 +50,12 @@ It would be okey for you to use any Antlr you like.
### Passing Dependencies
When executing Java code relying on these dependencies,
you can use `-cp` option of Java runtime to pass the directory where you can find those dependency's JAR files.
When compiling or executing Java code relying on these dependencies,
you can use `-cp` option to pass these dependency's JAR files.
All dependencies should be specified in one `-cp` option with `;` as separator.
For example, when compiling, you can pass `-cp "<path-to-antlr4-jar>;<path-to-gson-jar>"` as option.
Or pass `-cp ".;<path-to-antlr4-jar>;<path-to-gson-jar>"` when executing.
## Python

View File

@@ -34,3 +34,8 @@ add_custom_target (LibCmoDocuments
install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
DESTINATION ${NEMO_INSTALL_DOC_PATH}
)
# Install UNVIRT.md documentation
install(FILES ${CMAKE_CURRENT_LIST_DIR}/UNVIRT.md
DESTINATION ${NEMO_INSTALL_DOC_PATH}
)

View File

@@ -538,6 +538,121 @@ namespace LibCmo::VxMath {
#pragma endregion
#pragma region VxImageDescEx
VxImageDescEx::VxImageDescEx() : m_Width(0), m_Height(0), m_Image(nullptr) {}
VxImageDescEx::VxImageDescEx(CKDWORD width, CKDWORD height) : m_Width(width), m_Height(height), m_Image(nullptr) {
CreateImage(width, height);
}
YYCC_IMPL_COPY_CTOR(VxImageDescEx, rhs) : m_Width(rhs.m_Width), m_Height(rhs.m_Height), m_Image(nullptr) {
// copy image
if (rhs.m_Image != nullptr) {
CreateImage(rhs.m_Width, rhs.m_Height, rhs.m_Image);
}
}
YYCC_IMPL_COPY_OPER(VxImageDescEx, rhs) {
FreeImage();
m_Width = rhs.m_Width;
m_Height = rhs.m_Height;
if (rhs.m_Image != nullptr) {
CreateImage(rhs.m_Width, rhs.m_Height, rhs.m_Image);
}
return *this;
}
YYCC_IMPL_MOVE_CTOR(VxImageDescEx, rhs) : m_Width(rhs.m_Width), m_Height(rhs.m_Height), m_Image(rhs.m_Image) {
// move image
rhs.m_Height = 0;
rhs.m_Width = 0;
rhs.m_Image = nullptr;
}
YYCC_IMPL_MOVE_OPER(VxImageDescEx, rhs) {
FreeImage();
m_Height = rhs.m_Height;
m_Width = rhs.m_Width;
m_Image = rhs.m_Image;
rhs.m_Height = 0;
rhs.m_Width = 0;
rhs.m_Image = nullptr;
return *this;
}
VxImageDescEx::~VxImageDescEx() {
FreeImage();
}
void VxImageDescEx::CreateImage(CKDWORD Width, CKDWORD Height) {
FreeImage();
m_Width = Width;
m_Height = Height;
m_Image = new CKBYTE[GetImageSize()];
}
void VxImageDescEx::CreateImage(CKDWORD Width, CKDWORD Height, const void* dataptr) {
CreateImage(Width, Height);
std::memcpy(m_Image, dataptr, GetImageSize());
}
void VxImageDescEx::FreeImage() {
m_Width = 0;
m_Height = 0;
if (m_Image != nullptr) {
delete[] m_Image;
m_Image = nullptr;
}
}
CKDWORD VxImageDescEx::GetImageSize() const {
return static_cast<CKDWORD>(PIXEL_SIZE * m_Width * m_Height);
}
const CKBYTE* VxImageDescEx::GetImage() const { return m_Image; }
CKBYTE* VxImageDescEx::GetMutableImage() { return m_Image; }
CKDWORD VxImageDescEx::GetPixelCount() const {
return static_cast<CKDWORD>(m_Width * m_Height);
}
const CKDWORD* VxImageDescEx::GetPixels() const {
return reinterpret_cast<CKDWORD*>(m_Image);
}
CKDWORD* VxImageDescEx::GetMutablePixels() {
return reinterpret_cast<CKDWORD*>(m_Image);
}
CKDWORD VxImageDescEx::GetWidth() const { return m_Width; }
CKDWORD VxImageDescEx::GetHeight() const { return m_Height; }
bool VxImageDescEx::IsValid() const {
return (m_Width != 0u && m_Height != 0u && m_Image != nullptr);
}
bool VxImageDescEx::IsHWEqual(const VxImageDescEx& rhs) const {
return (m_Width == rhs.m_Width && m_Height == rhs.m_Height);
}
// bool VxImageDescEx::IsMaskEqual(const VxImageDescEx& rhs) const {
// return (
// m_RedMask == rhs.m_RedMask &&
// m_GreenMask == rhs.m_GreenMask &&
// m_BlueMask == rhs.m_BlueMask &&
// m_AlphaMask == rhs.m_AlphaMask
// );
// }
#pragma endregion
#pragma region Patched
namespace NSVxVector {

View File

@@ -341,53 +341,20 @@ namespace LibCmo::VxMath {
public:
static constexpr CKDWORD FACTOR_SIZE = 1u; /**< Single color factor (one of ARGB) occpied size in byte. */
static constexpr CKDWORD PIXEL_SIZE = FACTOR_SIZE * 4u; /**< Single pixel occpied size in byte. */
public:
/**
* @brief Create a blank (invalid) image.
*/
VxImageDescEx() : m_Width(0), m_Height(0), m_Image(nullptr) {}
VxImageDescEx();
/**
* @brief Create a image with given width and height.
* @param[in] width The width of image.
* @param[in] height The height of image.
*/
VxImageDescEx(CKDWORD width, CKDWORD height) : m_Width(width), m_Height(height), m_Image(nullptr) { CreateImage(width, height); }
VxImageDescEx(const VxImageDescEx& rhs) : m_Width(rhs.m_Width), m_Height(rhs.m_Height), m_Image(nullptr) {
// copy image
if (rhs.m_Image != nullptr) {
CreateImage(rhs.m_Width, rhs.m_Height, rhs.m_Image);
}
}
VxImageDescEx(VxImageDescEx&& rhs) noexcept : m_Width(rhs.m_Width), m_Height(rhs.m_Height), m_Image(rhs.m_Image) {
// move image
rhs.m_Height = 0;
rhs.m_Width = 0;
rhs.m_Image = nullptr;
}
VxImageDescEx& operator=(const VxImageDescEx& rhs) {
FreeImage();
m_Width = rhs.m_Width;
m_Height = rhs.m_Height;
if (rhs.m_Image != nullptr) {
CreateImage(rhs.m_Width, rhs.m_Height, rhs.m_Image);
}
return *this;
}
VxImageDescEx& operator=(VxImageDescEx&& rhs) noexcept {
FreeImage();
m_Height = rhs.m_Height;
m_Width = rhs.m_Width;
m_Image = rhs.m_Image;
rhs.m_Height = 0;
rhs.m_Width = 0;
rhs.m_Image = nullptr;
return *this;
}
~VxImageDescEx() { FreeImage(); }
VxImageDescEx(CKDWORD width, CKDWORD height);
~VxImageDescEx();
YYCC_DECL_COPY_MOVE(VxImageDescEx)
/**
* @brief Create image with given width and height
@@ -397,12 +364,7 @@ namespace LibCmo::VxMath {
* \li There is no initialization (fill with zero) for this new created image.
* \li Old image will be free first before creating.
*/
void CreateImage(CKDWORD Width, CKDWORD Height) {
FreeImage();
m_Width = Width;
m_Height = Height;
m_Image = new CKBYTE[GetImageSize()];
}
void CreateImage(CKDWORD Width, CKDWORD Height);
/**
* @brief Create image with given width, height and data.
* @param[in] Width The width of image.
@@ -413,86 +375,69 @@ namespace LibCmo::VxMath {
* an undefined behavior is raised.
* @remarks Old image will be free first before creating.
*/
void CreateImage(CKDWORD Width, CKDWORD Height, const void* dataptr) {
CreateImage(Width, Height);
std::memcpy(m_Image, dataptr, GetImageSize());
}
void CreateImage(CKDWORD Width, CKDWORD Height, const void* dataptr);
/**
* @brief Free current image. Reset this to invalid status.
*/
void FreeImage() {
m_Width = 0;
m_Height = 0;
if (m_Image != nullptr) {
delete[] m_Image;
m_Image = nullptr;
}
}
void FreeImage();
/**
* @brief Get the allocated memory size of image.
* @return The allocated memory size of image.
* Basically it is image width * height * (single pixel size).
*/
CKDWORD GetImageSize() const { return static_cast<CKDWORD>(PIXEL_SIZE * m_Width * m_Height); }
CKDWORD GetImageSize() const;
/**
* @brief Get a constant pointer to image in memory unit for viewing.
* @return A constant pointer to image in memory unit.
*/
const CKBYTE* GetImage() const { return m_Image; }
const CKBYTE* GetImage() const;
/**
* @brief Get a mutable pointer to image in memory unit for modifying.
* @return A mutable pointer to image in memory uint.
*/
CKBYTE* GetMutableImage() { return m_Image; }
CKBYTE* GetMutableImage();
/**
* @brief Get the full count of pixel in image.
* @return The count of image. Basically it is image width * height.
*/
CKDWORD GetPixelCount() const { return static_cast<CKDWORD>(m_Width * m_Height); }
CKDWORD GetPixelCount() const;
/**
* @brief Get a constant pointer to image in pixel unit for viewing.
* @return A constant pointer to image in pixel unit.
*/
const CKDWORD* GetPixels() const { return reinterpret_cast<CKDWORD*>(m_Image); }
const CKDWORD* GetPixels() const;
/**
* @brief Get a mutable pointer to image in pixel uint for modifying.
* @return A mutable pointer to image in pixel uint.
*/
CKDWORD* GetMutablePixels() { return reinterpret_cast<CKDWORD*>(m_Image); }
CKDWORD* GetMutablePixels();
/**
* @brief Get the width of this image in pixel.
* @return The width of this image in pixel.
*/
CKDWORD GetWidth() const { return m_Width; }
CKDWORD GetWidth() const;
/**
* @brief Get the height of this image in pixel.
* @return The height of this image in pixel.
*/
CKDWORD GetHeight() const { return m_Height; }
CKDWORD GetHeight() const;
/**
* @brief Check whether this image is valid image for using.
* @details If one of width and height is zero, or underlying image pointer, this image is invalid.
* @return True if it is, otherwise false.
*/
bool IsValid() const { return (m_Width != 0u && m_Height != 0u && m_Image != nullptr); }
bool IsValid() const;
/**
* @brief Check whether the width and height of this image are equal to another image.
* @param[in] rhs Another image for comparing.
* @return True if their width and height are equal, otherwise false.
*/
bool IsHWEqual(const VxImageDescEx& rhs) const { return (m_Width == rhs.m_Width && m_Height == rhs.m_Height); }
// bool IsMaskEqual(const VxImageDescEx& rhs) const {
// return (
// m_RedMask == rhs.m_RedMask &&
// m_GreenMask == rhs.m_GreenMask &&
// m_BlueMask == rhs.m_BlueMask &&
// m_AlphaMask == rhs.m_AlphaMask
// );
// }
bool IsHWEqual(const VxImageDescEx& rhs) const;
// bool IsMaskEqual(const VxImageDescEx& rhs) const;
//public:
// CKDWORD m_RedMask;