rpfm_extensions/diagnostics/
group_formations.rs1use getset::{Getters, MutGetters};
17use serde_derive::{Serialize, Deserialize};
18
19use std::{fmt, fmt::Display};
20
21use rpfm_lib::files::{RFile, RFileDecoded};
22use rpfm_lib::files::group_formations::validation::ValidationIssue;
23
24use crate::diagnostics::*;
25
26#[derive(Debug, Clone, Default, Getters, MutGetters, Serialize, Deserialize)]
32#[getset(get = "pub", get_mut = "pub")]
33pub struct GroupFormationsDiagnostic {
34 path: String,
35 pack: String,
36 results: Vec<GroupFormationsDiagnosticReport>
37}
38
39#[derive(Debug, Clone, Getters, MutGetters, Serialize, Deserialize)]
41#[getset(get = "pub", get_mut = "pub")]
42pub struct GroupFormationsDiagnosticReport {
43
44 formation_index: usize,
46
47 formation_name: String,
49
50 report_type: GroupFormationsDiagnosticReportType,
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub enum GroupFormationsDiagnosticReportType {
56 DuplicateFormationName,
57 NoAbsoluteBlock,
58 DuplicateBlockId(u32),
59 MissingReference(u32, u32),
60 ForwardReference(u32, u32),
61 ReferenceCycle(Vec<u32>),
62 EmptySpan(u32),
63 InvalidThresholds(u32),
64 NoEntityPreferences(u32),
65}
66
67impl GroupFormationsDiagnosticReport {
72 pub fn new(formation_index: usize, formation_name: &str, report_type: GroupFormationsDiagnosticReportType) -> Self {
73 Self {
74 formation_index,
75 formation_name: formation_name.to_owned(),
76 report_type,
77 }
78 }
79}
80
81impl GroupFormationsDiagnosticReportType {
82
83 pub fn block_id(&self) -> Option<u32> {
85 match self {
86 Self::DuplicateFormationName |
87 Self::NoAbsoluteBlock => None,
88 Self::DuplicateBlockId(block_id) |
89 Self::MissingReference(block_id, _) |
90 Self::ForwardReference(block_id, _) |
91 Self::EmptySpan(block_id) |
92 Self::InvalidThresholds(block_id) |
93 Self::NoEntityPreferences(block_id) => Some(*block_id),
94 Self::ReferenceCycle(block_ids) => block_ids.first().copied(),
95 }
96 }
97}
98
99impl From<ValidationIssue> for GroupFormationsDiagnosticReportType {
100 fn from(issue: ValidationIssue) -> Self {
101 match issue {
102 ValidationIssue::DuplicateFormationName => Self::DuplicateFormationName,
103 ValidationIssue::NoAbsoluteBlock => Self::NoAbsoluteBlock,
104 ValidationIssue::DuplicateBlockId(block_id) => Self::DuplicateBlockId(block_id),
105 ValidationIssue::MissingReference { block_id, referenced_id } => Self::MissingReference(block_id, referenced_id),
106 ValidationIssue::ForwardReference { block_id, referenced_id } => Self::ForwardReference(block_id, referenced_id),
107 ValidationIssue::ReferenceCycle(block_ids) => Self::ReferenceCycle(block_ids),
108 ValidationIssue::EmptySpan(block_id) => Self::EmptySpan(block_id),
109 ValidationIssue::InvalidThresholds(block_id) => Self::InvalidThresholds(block_id),
110 ValidationIssue::NoEntityPreferences(block_id) => Self::NoEntityPreferences(block_id),
111 }
112 }
113}
114
115impl DiagnosticReport for GroupFormationsDiagnosticReport {
116 fn message(&self) -> String {
117 let name = &self.formation_name;
118 match &self.report_type {
119 GroupFormationsDiagnosticReportType::DuplicateFormationName => format!("Formation '{name}': another formation has the same name."),
120 GroupFormationsDiagnosticReportType::NoAbsoluteBlock => format!("Formation '{name}': it has no absolute block, so nothing anchors its blocks."),
121 GroupFormationsDiagnosticReportType::DuplicateBlockId(block_id) => format!("Formation '{name}': more than one block uses the id {block_id}."),
122 GroupFormationsDiagnosticReportType::MissingReference(block_id, referenced_id) => format!("Formation '{name}': block {block_id} references block {referenced_id}, which doesn't exist."),
123 GroupFormationsDiagnosticReportType::ForwardReference(block_id, referenced_id) => format!("Formation '{name}': block {block_id} references block {referenced_id}, which is defined after it."),
124 GroupFormationsDiagnosticReportType::ReferenceCycle(block_ids) => {
125 let block_ids = block_ids.iter().map(|block_id| block_id.to_string()).collect::<Vec<_>>().join(", ");
126 format!("Formation '{name}': blocks {block_ids} depend on each other in a loop.")
127 },
128 GroupFormationsDiagnosticReportType::EmptySpan(block_id) => format!("Formation '{name}': span {block_id} has no members."),
129 GroupFormationsDiagnosticReportType::InvalidThresholds(block_id) => format!("Formation '{name}': block {block_id} has a minimum amount of units above its maximum."),
130 GroupFormationsDiagnosticReportType::NoEntityPreferences(block_id) => format!("Formation '{name}': block {block_id} has no entity preferences, so no unit can be placed in it."),
131 }
132 }
133
134 fn level(&self) -> DiagnosticLevel {
135 match self.report_type {
136 GroupFormationsDiagnosticReportType::NoAbsoluteBlock |
137 GroupFormationsDiagnosticReportType::DuplicateBlockId(_) |
138 GroupFormationsDiagnosticReportType::MissingReference(_, _) |
139 GroupFormationsDiagnosticReportType::ReferenceCycle(_) => DiagnosticLevel::Error,
140 GroupFormationsDiagnosticReportType::DuplicateFormationName |
141 GroupFormationsDiagnosticReportType::ForwardReference(_, _) |
142 GroupFormationsDiagnosticReportType::EmptySpan(_) |
143 GroupFormationsDiagnosticReportType::InvalidThresholds(_) |
144 GroupFormationsDiagnosticReportType::NoEntityPreferences(_) => DiagnosticLevel::Warning,
145 }
146 }
147}
148
149impl Display for GroupFormationsDiagnosticReportType {
150 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151 Display::fmt(match self {
152 Self::DuplicateFormationName => "GroupFormationsDuplicateFormationName",
153 Self::NoAbsoluteBlock => "GroupFormationsNoAbsoluteBlock",
154 Self::DuplicateBlockId(_) => "GroupFormationsDuplicateBlockId",
155 Self::MissingReference(_, _) => "GroupFormationsMissingReference",
156 Self::ForwardReference(_, _) => "GroupFormationsForwardReference",
157 Self::ReferenceCycle(_) => "GroupFormationsReferenceCycle",
158 Self::EmptySpan(_) => "GroupFormationsEmptySpan",
159 Self::InvalidThresholds(_) => "GroupFormationsInvalidThresholds",
160 Self::NoEntityPreferences(_) => "GroupFormationsNoEntityPreferences",
161 }, f)
162 }
163}
164
165impl GroupFormationsDiagnostic {
166 pub fn new(path: &str, pack: &str) -> Self {
167 Self {
168 path: path.to_owned(),
169 pack: pack.to_owned(),
170 results: vec![],
171 }
172 }
173
174 pub fn check(
186 pack_key: &str,
187 file: &RFile,
188 global_ignored_diagnostics: &[String],
189 ) -> Option<DiagnosticType> {
190 let Ok(RFileDecoded::GroupFormations(data)) = file.decoded() else { return None };
191
192 let mut diagnostic = GroupFormationsDiagnostic::new(file.path_in_container_raw(), pack_key);
193 for (formation_index, issue) in data.validate() {
194 let report_type = GroupFormationsDiagnosticReportType::from(issue);
195 if global_ignored_diagnostics.contains(&report_type.to_string()) {
196 continue;
197 }
198
199 let formation_name = data.formations().get(formation_index).map(|formation| formation.name().as_str()).unwrap_or_default();
200 diagnostic.results_mut().push(GroupFormationsDiagnosticReport::new(formation_index, formation_name, report_type));
201 }
202
203 if diagnostic.results().is_empty() {
204 None
205 } else {
206 Some(DiagnosticType::GroupFormations(diagnostic))
207 }
208 }
209}