Skip to content

Commit 29b4f0d

Browse files
committed
Report added topics not defined in the checked file
1 parent 2007dcb commit 29b4f0d

File tree

4 files changed

+96
-43
lines changed

4 files changed

+96
-43
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "StandardsValidator"
3-
version = "2.17.0"
3+
version = "2.18.0"
44
edition = "2021"
55

66
[dependencies]

WARNINGS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@ The faction argument is optional because it defaults to the speaker's faction, b
315315
### Contains superfluous characters in a ModPCFacRep call
316316
This line includes commas or puts quotes around the reputation amount. This is pointless and makes it harder to find this line in the CS.
317317

318+
### Adds topic which is not defined in this file
319+
`AddTopic X` crashes Morrowind.exe if topic `X` does not exist. If the topic is defined in a master file, all is well.
320+
318321
## Magic
319322

320323
### Uses effect

src/validators/scripts.rs

Lines changed: 91 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
};
1212
use codegen::{get_joined_commands, get_khajiit_script};
1313
use regex::{Regex, RegexBuilder};
14-
use tes3::esp::{Cell, Dialogue, Npc, NpcFlags, Reference, Script, TES3Object};
14+
use tes3::esp::{Cell, Dialogue, DialogueType2, Npc, NpcFlags, Reference, Script, TES3Object};
1515

1616
enum PositionMarkerType {
1717
Unknown,
@@ -39,6 +39,9 @@ pub struct ScriptValidator {
3939
marker_id: Regex,
4040
mod_reputation: Regex,
4141
mod_facrep: Regex,
42+
add_topic: Regex,
43+
added_topics: HashMap<String, Vec<String>>,
44+
topics: HashSet<String>,
4245
}
4346

4447
struct ScriptInfo {
@@ -70,54 +73,64 @@ impl Handler<'_> for ScriptValidator {
7073
if context.mode == Mode::Vanilla {
7174
return;
7275
}
73-
if let TES3Object::Script(script) = record {
74-
let text = &script.text;
75-
let mut info = ScriptInfo::new(
76-
self.npc.is_match(text),
77-
self.khajiit.is_match(text),
78-
self.nolore.is_match(text),
79-
self.vampire.is_match(text),
80-
);
81-
for (local, regex) in &self.projects {
82-
if regex.is_match(text) {
83-
info.projects.push(local);
76+
match record {
77+
TES3Object::Script(script) => {
78+
let text = &script.text;
79+
let mut info = ScriptInfo::new(
80+
self.npc.is_match(text),
81+
self.khajiit.is_match(text),
82+
self.nolore.is_match(text),
83+
self.vampire.is_match(text),
84+
);
85+
for (local, regex) in &self.projects {
86+
if regex.is_match(text) {
87+
info.projects.push(local);
88+
}
89+
}
90+
if info.khajiit && !self.has_correct_khajiit_check(script, text) {
91+
println!("Script {} contains non-standard khajiit check", script.id);
92+
}
93+
self.scripts.insert(script.id.to_ascii_lowercase(), info);
94+
if let Some(captures) = self.commands.captures(text) {
95+
println!(
96+
"Script {} contains line {}",
97+
script.id,
98+
captures.get(0).unwrap().as_str()
99+
);
84100
}
85101
}
86-
if info.khajiit && !self.has_correct_khajiit_check(script, text) {
87-
println!("Script {} contains non-standard khajiit check", script.id);
88-
}
89-
self.scripts.insert(script.id.to_ascii_lowercase(), info);
90-
if let Some(captures) = self.commands.captures(text) {
91-
println!(
92-
"Script {} contains line {}",
93-
script.id,
94-
captures.get(0).unwrap().as_str()
95-
);
102+
TES3Object::Npc(npc) => {
103+
if !npc.is_dead() {
104+
if npc.script.is_empty() {
105+
println!("Npc {} does not have a script", npc.id);
106+
} else {
107+
self.check_npc_script(npc);
108+
}
109+
}
96110
}
97-
} else if let TES3Object::Npc(npc) = record {
98-
if !npc.is_dead() {
99-
if npc.script.is_empty() {
100-
println!("Npc {} does not have a script", npc.id);
111+
TES3Object::Book(book) => {
112+
let id = book.id.to_ascii_lowercase();
113+
let marker = if book.mesh.eq_ignore_ascii_case(NPC_MARKER) {
114+
PositionMarkerType::NpcMarker
115+
} else if is_marker(book) {
116+
PositionMarkerType::Marker
101117
} else {
102-
self.check_npc_script(npc);
118+
PositionMarkerType::Book
119+
};
120+
if let Some((_, marker_type, _, _)) = self.markers.get_mut(&id) {
121+
*marker_type = marker;
122+
} else if let Some(found) = self.marker_id.find(&book.id) {
123+
if found.len() == book.id.len() {
124+
self.markers.insert(id, (String::new(), marker, false, 0));
125+
}
103126
}
104127
}
105-
} else if let TES3Object::Book(book) = record {
106-
let id = book.id.to_ascii_lowercase();
107-
let marker = if book.mesh.eq_ignore_ascii_case(NPC_MARKER) {
108-
PositionMarkerType::NpcMarker
109-
} else if is_marker(book) {
110-
PositionMarkerType::Marker
111-
} else {
112-
PositionMarkerType::Book
113-
};
114-
if let Some((_, marker_type, _, _)) = self.markers.get_mut(&id) {
115-
*marker_type = marker;
116-
} else if let Some(found) = self.marker_id.find(&book.id) {
117-
if found.len() == book.id.len() {
118-
self.markers.insert(id, (String::new(), marker, false, 0));
128+
TES3Object::Dialogue(dial) => {
129+
if dial.dialogue_type == DialogueType2::Topic {
130+
self.topics.insert(dial.id.to_ascii_lowercase());
119131
}
120132
}
133+
_ => {}
121134
}
122135
}
123136

@@ -219,6 +232,28 @@ impl Handler<'_> for ScriptValidator {
219232
}
220233
}
221234
}
235+
if let Some(captures) = self.add_topic.captures(code) {
236+
let mut capture = captures.get(3);
237+
if capture.is_none() {
238+
capture = captures.get(4);
239+
}
240+
if let Some(string) = capture {
241+
let id = string.as_str().to_ascii_lowercase();
242+
let description = if let TES3Object::DialogueInfo(info) = record {
243+
format!("Info {} in topic {}", info.id, topic.id)
244+
} else if let TES3Object::Script(script) = record {
245+
format!("Script {}", script.id)
246+
} else {
247+
String::new()
248+
};
249+
let entry = self.added_topics.get_mut(&id);
250+
if let Some(sources) = entry {
251+
sources.push(description);
252+
} else {
253+
self.added_topics.insert(id, vec![description]);
254+
}
255+
}
256+
}
222257
}
223258

224259
fn on_cellref(
@@ -278,6 +313,17 @@ impl Handler<'_> for ScriptValidator {
278313
);
279314
}
280315
}
316+
for (topic, sources) in &self.added_topics {
317+
if self.topics.contains(topic) {
318+
continue;
319+
}
320+
for source in sources {
321+
println!(
322+
"{} adds topic {} which is not defined in this file",
323+
source, topic
324+
);
325+
}
326+
}
281327
}
282328
}
283329

@@ -337,6 +383,7 @@ impl ScriptValidator {
337383
.build()?;
338384
let mod_reputation = Regex::new(r"^[,\s]*modreputation[,\s]")?;
339385
let mod_facrep = Regex::new(r#"modpcfacrep[,\s]+([0-9"-]+)([,\s]+([^,\s]+))?[,\s]*$"#)?;
386+
let add_topic = Regex::new(r#"^([,\s]*|.*?->[,\s]*)addtopic[,\s]+("([^"]+)"|([^\s"]+))"#)?;
340387
Ok(Self {
341388
unique_heads,
342389
scripts: HashMap::new(),
@@ -356,6 +403,9 @@ impl ScriptValidator {
356403
markers: HashMap::new(),
357404
mod_reputation,
358405
mod_facrep,
406+
add_topic,
407+
added_topics: HashMap::new(),
408+
topics: HashSet::new(),
359409
})
360410
}
361411

0 commit comments

Comments
 (0)