feat: add new rules for BMapInspector
This commit is contained in:
@@ -99,7 +99,7 @@ where
|
||||
i: usize,
|
||||
_p: PhantomData<P>,
|
||||
/// Phantom reference to prevent object modification during iteration
|
||||
_o: &'o O,
|
||||
_o: PhantomData<&'o O>,
|
||||
}
|
||||
|
||||
impl<'o, P, O, T> StructIterator<'o, P, O, T>
|
||||
@@ -567,7 +567,7 @@ where
|
||||
|
||||
pub trait BMTexture<'o, P>: BMObject<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
fn get_file_name(&self) -> Result<Option<String>> {
|
||||
get_string_value(self, bmap::BMTexture_GetFileName)
|
||||
@@ -767,7 +767,7 @@ where
|
||||
|
||||
pub trait BMMesh<'o, P>: BMObject<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
fn get_lit_mode(&self) -> Result<bmap::VXMESH_LITMODE> {
|
||||
get_copyable_value(self, bmap::BMMesh_GetLitMode)
|
||||
@@ -775,12 +775,51 @@ where
|
||||
fn set_lit_mode(&mut self, data: bmap::VXMESH_LITMODE) -> Result<()> {
|
||||
set_copyable_value(self, bmap::BMMesh_SetLitMode, data)
|
||||
}
|
||||
|
||||
fn get_vertex_count(&self) -> Result<u32> {
|
||||
get_copyable_value(self, bmap::BMMesh_GetVertexCount)
|
||||
}
|
||||
fn set_vertex_count(&mut self, count: u32) -> Result<()> {
|
||||
set_copyable_value(self, bmap::BMMesh_SetVertexCount, count)
|
||||
}
|
||||
fn get_vertex_positions(&'o self) -> Result<StructIterator<'o, P, Self, bmap::VxVector3>> {
|
||||
let ptr = get_copyable_value(self, bmap::BMMesh_GetVertexPositions)?;
|
||||
struct_iterator(self, ptr, self.get_vertex_count()?.try_into()?)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BM3dEntity<'o, P>: BMObject<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
fn get_world_matrix(&self) -> Result<bmap::VxMatrix> {
|
||||
get_copyable_value(self, bmap::BM3dEntity_GetWorldMatrix)
|
||||
}
|
||||
fn set_world_matrix(&mut self, mat: bmap::VxMatrix) -> Result<()> {
|
||||
set_copyable_value(self, bmap::BM3dEntity_SetWorldMatrix, mat)
|
||||
}
|
||||
|
||||
// YYC MARK:
|
||||
// Same reason for the reuse of "value setter" and "value getter".
|
||||
fn get_current_mesh(&self) -> Result<Option<Box<dyn BMMesh<'o, P> + 'o>>> {
|
||||
let ckid: CKID = get_copyable_value(self, bmap::BM3dEntity_GetCurrentMesh)?;
|
||||
Ok(if ckid == INVALID_CKID {
|
||||
None
|
||||
} else {
|
||||
Some(Box::new(BMMeshImpl::<'o, P>::new(
|
||||
unsafe { self.get_pointer() },
|
||||
ckid,
|
||||
)))
|
||||
})
|
||||
}
|
||||
fn set_current_mesh(&mut self, mesh: Option<&dyn BMMesh<'o, P>>) -> Result<()> {
|
||||
let ckid: CKID = match mesh {
|
||||
Some(mesh) => unsafe { mesh.get_ckid() },
|
||||
None => INVALID_CKID,
|
||||
};
|
||||
set_copyable_value(self, bmap::BM3dEntity_SetCurrentMesh, ckid)
|
||||
}
|
||||
|
||||
fn get_visibility(&self) -> Result<bool> {
|
||||
get_copyable_value(self, bmap::BM3dEntity_GetVisibility)
|
||||
}
|
||||
@@ -791,13 +830,13 @@ where
|
||||
|
||||
pub trait BM3dObject<'o, P>: BM3dEntity<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
}
|
||||
|
||||
pub trait BMLight<'o, P>: BM3dEntity<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
fn get_type(&self) -> Result<bmap::VXLIGHT_TYPE> {
|
||||
get_copyable_value(self, bmap::BMLight_GetType)
|
||||
@@ -861,13 +900,13 @@ where
|
||||
|
||||
pub trait BMTargetLight<'o, P>: BMLight<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
}
|
||||
|
||||
pub trait BMCamera<'o, P>: BM3dEntity<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
fn get_projection_type(&self) -> Result<bmap::CK_CAMERA_PROJECTION> {
|
||||
get_copyable_value(self, bmap::BMCamera_GetProjectionType)
|
||||
@@ -926,13 +965,13 @@ where
|
||||
|
||||
pub trait BMTargetCamera<'o, P>: BMCamera<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
}
|
||||
|
||||
pub trait BMGroup<'o, P>: BMObject<'o, P>
|
||||
where
|
||||
P: AbstractPointer<'o> + ?Sized,
|
||||
P: AbstractPointer<'o> + ?Sized + 'o,
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace BMapInspector::Rule {
|
||||
rules.emplace_back(new YYCRule2());
|
||||
rules.emplace_back(new BBugRule1());
|
||||
rules.emplace_back(new ZZQRule1());
|
||||
rules.emplace_back(new ZZQRule2());
|
||||
rules.emplace_back(new SOneRule1());
|
||||
rules.emplace_back(new SSBRule1());
|
||||
rules.emplace_back(new LXRule1());
|
||||
|
||||
@@ -78,9 +78,9 @@ namespace BMapInspector::Rule {
|
||||
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::RenderObjectName(other_object),
|
||||
Shared::RenderObjectName(mesh));
|
||||
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());
|
||||
} else {
|
||||
// If not, check material.
|
||||
// Iterate all meshes
|
||||
@@ -89,10 +89,10 @@ namespace BMapInspector::Rule {
|
||||
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::RenderObjectName(other_object),
|
||||
Shared::RenderObjectName(mtl),
|
||||
Shared::RenderObjectName(mesh));
|
||||
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());
|
||||
} else {
|
||||
// Still not, check texture.
|
||||
// Fetch texture
|
||||
@@ -102,11 +102,11 @@ namespace BMapInspector::Rule {
|
||||
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::RenderObjectName(other_object),
|
||||
Shared::RenderObjectName(texture),
|
||||
Shared::RenderObjectName(mesh),
|
||||
Shared::RenderObjectName(mtl));
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ namespace BMapInspector::Rule {
|
||||
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::RenderObjectName(physicalized_3dobject));
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ namespace BMapInspector::Rule {
|
||||
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::RenderObjectName(physicalized_3dobject));
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "Shared.hpp"
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/op.hpp>
|
||||
#include <yycc/carton/termcolor.hpp>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
@@ -101,7 +99,7 @@ namespace BMapInspector::Rule::Shared {
|
||||
return rv;
|
||||
}
|
||||
|
||||
const char8_t* RenderObjectName(O::CKObject* obj) {
|
||||
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) {
|
||||
@@ -111,6 +109,14 @@ namespace BMapInspector::Rule::Shared {
|
||||
}
|
||||
}
|
||||
|
||||
std::u8string QuoteObjectName(O::CKObject* obj) {
|
||||
std::u8string rv;
|
||||
rv.push_back(u8'"');
|
||||
rv.append(RenderObjectName(obj));
|
||||
rv.push_back(u8'"');
|
||||
return rv;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace BMapInspector::Rule::Shared
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
#include <VTAll.hpp>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/op.hpp>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
namespace BMapInspector::Rule::Shared {
|
||||
|
||||
@@ -59,7 +62,7 @@ namespace BMapInspector::Rule::Shared {
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Utility Classes
|
||||
#pragma region Utility Classes
|
||||
|
||||
constexpr L::CKDWORD MIN_SECTOR = 1;
|
||||
constexpr L::CKDWORD MAX_SECTOR = 999;
|
||||
@@ -141,7 +144,25 @@ namespace BMapInspector::Rule::Shared {
|
||||
* @param[in] obj Can not be nullptr.
|
||||
* @return
|
||||
*/
|
||||
const char8_t* RenderObjectName(O::CKObject* obj);
|
||||
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) {
|
||||
return yycc::string::op::join(
|
||||
[&first, &last]() -> std::optional<std::u8string_view> {
|
||||
if (first == last) return std::nullopt;
|
||||
return QuoteObjectName(*(first++));
|
||||
},
|
||||
u8", ");
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
@@ -48,11 +48,11 @@ namespace BMapInspector::Rule {
|
||||
// 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::RenderObjectName(group_3dobject),
|
||||
Shared::RenderObjectName(texture),
|
||||
Shared::RenderObjectName(mesh),
|
||||
Shared::RenderObjectName(mtl));
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +74,9 @@ namespace BMapInspector::Rule {
|
||||
// Report error.
|
||||
reporter.FormatError(
|
||||
YYC1,
|
||||
u8R"(Object "%s" is not grouped into Phys_FloorRails, but some objects grouped into Phys_FloorRails refer its mesh "%s". This will cause this object be smooth unexpectly.)",
|
||||
Shared::RenderObjectName(obj),
|
||||
Shared::RenderObjectName(mesh));
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,9 +120,9 @@ namespace BMapInspector::Rule {
|
||||
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::RenderObjectName(physicalized_3dobject),
|
||||
Shared::RenderObjectName(mesh));
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "ZZQRules.hpp"
|
||||
#include "Shared.hpp"
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
namespace L = LibCmo;
|
||||
namespace C = LibCmo::CK2;
|
||||
@@ -33,8 +36,8 @@ namespace BMapInspector::Rule {
|
||||
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::RenderObjectName(first_3dobjects));
|
||||
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
|
||||
@@ -42,8 +45,101 @@ namespace BMapInspector::Rule {
|
||||
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::RenderObjectName(other_3dobject));
|
||||
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);
|
||||
|
||||
// Join object together
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,21 @@ namespace BMapInspector::Rule {
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ZZQ Rule 2
|
||||
* @details
|
||||
* The Ballance should only be included only one group.
|
||||
* This rule will check whether there is intersection between different sector group.
|
||||
*/
|
||||
class ZZQRule2 : public IRule {
|
||||
public:
|
||||
ZZQRule2();
|
||||
virtual ~ZZQRule2();
|
||||
YYCC_DEFAULT_COPY_MOVE(ZZQRule2)
|
||||
|
||||
public:
|
||||
std::u8string_view GetRuleName() const override;
|
||||
void Check(Reporter::Reporter& reporter, Map::Level& level) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user