diff --git a/Assets/BMapBindings/bmap-rs/src/bmap_wrapper.rs b/Assets/BMapBindings/bmap-rs/src/bmap_wrapper.rs index b2d4cfb..bcf79fd 100644 --- a/Assets/BMapBindings/bmap-rs/src/bmap_wrapper.rs +++ b/Assets/BMapBindings/bmap-rs/src/bmap_wrapper.rs @@ -99,7 +99,7 @@ where i: usize, _p: PhantomData

, /// 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> { 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 { 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 { + 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> { + 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 { + 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 + '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 { 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 { 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 { 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, { } diff --git a/Ballance/BMapInspector/Rule.cpp b/Ballance/BMapInspector/Rule.cpp index 6aa3da5..6e7f36e 100644 --- a/Ballance/BMapInspector/Rule.cpp +++ b/Ballance/BMapInspector/Rule.cpp @@ -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()); diff --git a/Ballance/BMapInspector/Rule/LXRules.cpp b/Ballance/BMapInspector/Rule/LXRules.cpp index e201b98..741618a 100644 --- a/Ballance/BMapInspector/Rule/LXRules.cpp +++ b/Ballance/BMapInspector/Rule/LXRules.cpp @@ -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()); } } } diff --git a/Ballance/BMapInspector/Rule/SOneRules.cpp b/Ballance/BMapInspector/Rule/SOneRules.cpp index 3c2a5ff..8c1912c 100644 --- a/Ballance/BMapInspector/Rule/SOneRules.cpp +++ b/Ballance/BMapInspector/Rule/SOneRules.cpp @@ -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()); } } } diff --git a/Ballance/BMapInspector/Rule/SSBRules.cpp b/Ballance/BMapInspector/Rule/SSBRules.cpp index f73121e..ef3e512 100644 --- a/Ballance/BMapInspector/Rule/SSBRules.cpp +++ b/Ballance/BMapInspector/Rule/SSBRules.cpp @@ -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()); } } } diff --git a/Ballance/BMapInspector/Rule/Shared.cpp b/Ballance/BMapInspector/Rule/Shared.cpp index 83ca2aa..2e59436 100644 --- a/Ballance/BMapInspector/Rule/Shared.cpp +++ b/Ballance/BMapInspector/Rule/Shared.cpp @@ -1,6 +1,4 @@ #include "Shared.hpp" -#include -#include #include #include #include @@ -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"", 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 diff --git a/Ballance/BMapInspector/Rule/Shared.hpp b/Ballance/BMapInspector/Rule/Shared.hpp index 7a71ca0..99dc965 100644 --- a/Ballance/BMapInspector/Rule/Shared.hpp +++ b/Ballance/BMapInspector/Rule/Shared.hpp @@ -1,7 +1,10 @@ #pragma once #include +#include +#include #include #include +#include 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 + requires std::is_pointer_v> + && std::is_base_of_v>> + std::u8string QuoteObjectNames(InputIt first, InputIt last) { + return yycc::string::op::join( + [&first, &last]() -> std::optional { + if (first == last) return std::nullopt; + return QuoteObjectName(*(first++)); + }, + u8", "); + } #pragma endregion diff --git a/Ballance/BMapInspector/Rule/YYCRules.cpp b/Ballance/BMapInspector/Rule/YYCRules.cpp index eabb7f3..c021752 100644 --- a/Ballance/BMapInspector/Rule/YYCRules.cpp +++ b/Ballance/BMapInspector/Rule/YYCRules.cpp @@ -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()); } } } diff --git a/Ballance/BMapInspector/Rule/ZZQRules.cpp b/Ballance/BMapInspector/Rule/ZZQRules.cpp index 6e068b8..4abdcce 100644 --- a/Ballance/BMapInspector/Rule/ZZQRules.cpp +++ b/Ballance/BMapInspector/Rule/ZZQRules.cpp @@ -1,5 +1,8 @@ #include "ZZQRules.hpp" #include "Shared.hpp" +#include +#include +#include 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> sector_objects; + for (L::CKDWORD i = Shared::MIN_SECTOR; i <= Shared::MAX_SECTOR; ++i) { + // Prepare inserted object set. + std::set 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 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(i + 1); + auto right_sector_idx = static_cast(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()); + } + } } } diff --git a/Ballance/BMapInspector/Rule/ZZQRules.hpp b/Ballance/BMapInspector/Rule/ZZQRules.hpp index 3c8eb69..39f1384 100644 --- a/Ballance/BMapInspector/Rule/ZZQRules.hpp +++ b/Ballance/BMapInspector/Rule/ZZQRules.hpp @@ -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; + }; + }