1use crate::prelude::*;
8
9use alloc::collections::BTreeMap;
10
11use super::{Lang, LangCode};
12use crate::dialogue::{DialogueTable, REPLY_SPEAKER, Step, caption_key};
13use crate::names::Names;
14
15pub(super) const NAMES_PREFIX: &str = "names.";
17
18pub fn names_key(id: &str) -> String {
20 format!("{NAMES_PREFIX}{id}")
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum LineKind {
26 Speech,
28 Reply,
30 Caption,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct SourceLine {
37 pub scene: String,
39 pub text: String,
41 pub kind: LineKind,
43 pub file: String,
46}
47
48#[derive(Debug, Clone, Default, PartialEq, Eq)]
51pub struct SourceText {
52 pub data: BTreeMap<String, String>,
55 pub lines: BTreeMap<String, SourceLine>,
57 pub names: Names,
59}
60
61fn file_stem(path: &str) -> &str {
63 let name = path.rsplit(['/', '\\']).next().unwrap_or(path);
64 name.strip_suffix(".dlg").unwrap_or(name)
65}
66
67fn add_captions<D>(
69 scene: &str,
70 file: &str,
71 steps: &[Step<D>],
72 lines: &mut BTreeMap<String, SourceLine>,
73) {
74 for step in steps {
75 match step {
76 Step::Caption { text } => {
77 let line = SourceLine {
78 scene: scene.to_owned(),
79 text: text.clone(),
80 kind: LineKind::Caption,
81 file: file.to_owned(),
82 };
83 lines.insert(caption_key(scene, text), line);
84 }
85 Step::Choice { options } => {
86 for option in options {
87 add_captions(scene, file, &option.steps, lines);
88 }
89 }
90 Step::If {
91 then, otherwise, ..
92 } => {
93 add_captions(scene, file, then, lines);
94 add_captions(scene, file, otherwise, lines);
95 }
96 Step::Place { .. }
97 | Step::Clear { .. }
98 | Step::Say { .. }
99 | Step::Narrate { .. }
100 | Step::Music(_)
101 | Step::Directive(_) => {}
102 }
103 }
104}
105
106impl SourceText {
107 pub fn new(mut data: BTreeMap<String, String>, names: Names) -> Self {
110 for (id, value) in &names.names {
111 data.insert(names_key(id), value.clone());
112 }
113 Self {
114 data,
115 lines: BTreeMap::new(),
116 names,
117 }
118 }
119
120 pub fn add_dialogue<D>(&mut self, dialogue: &DialogueTable<D>) {
122 for scene in dialogue.scenes.values() {
123 let file = dialogue
124 .files
125 .get(&scene.id)
126 .map_or("", |path| file_stem(path));
127 for line in scene.lines() {
128 let kind = if line.speaker == REPLY_SPEAKER {
129 LineKind::Reply
130 } else {
131 LineKind::Speech
132 };
133 let source = SourceLine {
134 scene: scene.id.clone(),
135 text: line.text.to_owned(),
136 kind,
137 file: file.to_owned(),
138 };
139 self.lines.insert(line.id.as_str().to_owned(), source);
140 }
141 add_captions(&scene.id, file, &scene.steps, &mut self.lines);
142 }
143 }
144}
145
146impl Lang {
147 pub fn data_text<'a>(
152 &'a self,
153 code: &LangCode,
154 key: impl FnOnce() -> String,
155 english: &'a str,
156 ) -> &'a str {
157 let mut packs = self.packs_along(code).peekable();
158 if packs.peek().is_none() {
159 return english;
160 }
161 let key = key();
162 packs
163 .find_map(|(_, pack)| pack.data.get(&key).filter(|entry| entry.source == english))
164 .map_or(english, |entry| &entry.text)
165 }
166
167 pub fn name<'a>(&'a self, code: &LangCode, id: &str, english: &'a str) -> &'a str {
170 self.data_text(code, || names_key(id), english)
171 }
172
173 pub fn names(&self, code: &LangCode, names: &Names) -> Names {
177 if self.packs_along(code).next().is_none() {
178 return names.clone();
179 }
180 let translated = |(id, english): (&String, &String)| {
181 (id.clone(), self.name(code, id, english).to_owned())
182 };
183 Names {
184 names: names.names.iter().map(translated).collect(),
185 }
186 }
187
188 pub fn found<'a>(&'a self, code: &LangCode, prefix: &str, english: &'a str) -> &'a str {
194 let mut packs = self.packs_along(code).peekable();
195 if packs.peek().is_none() {
196 return english;
197 }
198 let end = format!("{prefix}\u{10ffff}");
199 let keys = self
200 .source
201 .data
202 .range(prefix.to_owned()..end)
203 .filter(|&(_, today)| today == english);
204 packs
205 .find_map(|(_, pack)| {
206 keys.clone()
207 .find_map(|(key, today)| pack.data.get(key).filter(|e| e.source == *today))
208 })
209 .map_or(english, |entry| entry.text.as_str())
210 }
211}