1use std::collections::HashSet;
2use std::fs;
3use std::io::Write;
4use std::path::{Path, PathBuf};
5
6mod board_config;
7pub use board_config::{BoardConfig, parse_board_config, write_board_config};
8
9pub fn generate_board_types_file(boards_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
15 let board_types_path = boards_dir.join("board_types.txt");
16 let mut file = fs::File::create(&board_types_path)?;
17
18 let valid_boards = get_valid_board_directories(boards_dir);
20
21 for board_dir in valid_boards {
23 if let Some(board_name) = board_dir.file_name().and_then(|n| n.to_str()) {
24 writeln!(
25 file,
26 "board\n{{\n\tboard_config_file = \"data/boards/{board_name}/boardcnf.txt\";\n}};",
27 )?;
28 }
29 }
30
31 Ok(())
32}
33
34#[must_use]
35pub fn get_valid_board_directories(boards_dir: &Path) -> Vec<PathBuf> {
36 let mut board_dirs = Vec::new();
37
38 if let Ok(entries) = fs::read_dir(boards_dir) {
39 for entry in entries.flatten() {
40 let path = entry.path();
41 if path.is_dir() && board_has_config(&path) {
42 board_dirs.push(path);
43 }
44 }
45 }
46
47 natural_sort(&mut board_dirs);
49 board_dirs
50}
51
52#[must_use]
53pub fn board_has_config(board_dir: &Path) -> bool {
54 let board_config_path = board_dir.join("boardcnf.txt");
55 board_config_path.exists()
56}
57
58#[must_use]
59pub fn get_available_boards(game_path: &Path) -> std::collections::HashSet<String> {
60 let available_boards_path = game_path.join("Saved_Data/available_boards.txt");
61 let mut available = std::collections::HashSet::new();
62
63 if let Ok(content) = fs::read_to_string(available_boards_path) {
64 for line in content.lines() {
65 let line = line.trim();
66 if line.is_empty() {
67 continue;
68 }
69
70 if let Some(comma_pos) = line.find(',') {
72 let board_part = &line[..comma_pos];
73 let board_name = board_part.trim_matches('"');
74
75 let rest = &line[comma_pos + 1..];
77
78 let status_part = if let Some(second_comma_pos) = rest.find(',') {
80 &rest[..second_comma_pos]
81 } else {
82 rest.trim()
83 };
84
85 if status_part.trim() == "1" {
87 available.insert(board_name.to_string());
88 }
89 }
90 }
91 }
92
93 available
94}
95
96pub fn set_board_available(
102 game_path: &Path,
103 board_name: &str,
104 available: bool,
105) -> Result<(), Box<dyn std::error::Error>> {
106 let available_boards_path = game_path.join("Saved_Data/available_boards.txt");
107 let mut available_boards = get_available_boards(game_path);
108
109 if available {
110 available_boards.insert(board_name.to_string());
111 } else {
112 available_boards.remove(board_name);
113 }
114
115 let boards_dir = game_path.join("data/boards");
117 let all_valid_boards = get_valid_board_directories(&boards_dir);
118
119 write_available_boards_file(&available_boards_path, &available_boards, &all_valid_boards)?;
121
122 Ok(())
123}
124
125pub fn generate_all_boards_files(
131 boards_dir: &Path,
132 game_path: &Path,
133) -> Result<(), Box<dyn std::error::Error>> {
134 generate_board_types_file(boards_dir)?;
136
137 let available_boards_path = game_path.join("Saved_Data/available_boards.txt");
139
140 if !available_boards_path.exists() {
141 let valid_boards = get_valid_board_directories(boards_dir);
143
144 if let Some(parent) = available_boards_path.parent() {
146 fs::create_dir_all(parent)?;
147 }
148
149 let mut available_boards = HashSet::new();
151 for board_dir in &valid_boards {
152 if let Some(board_name) = board_dir.file_name().and_then(|n| n.to_str()) {
153 available_boards.insert(board_name.to_string());
154 }
155 }
156
157 write_available_boards_file(&available_boards_path, &available_boards, &valid_boards)?;
159 }
160
161 Ok(())
162}
163
164fn write_available_boards_file(
165 available_boards_path: &Path,
166 available_boards: &HashSet<String>,
167 all_boards: &[PathBuf],
168) -> Result<(), Box<dyn std::error::Error>> {
169 if let Some(parent) = available_boards_path.parent() {
171 fs::create_dir_all(parent)?;
172 }
173
174 let mut file = fs::File::create(available_boards_path)?;
175
176 writeln!(file, "{{")?;
177
178 for (index, board_dir) in all_boards.iter().enumerate() {
180 if let Some(board_name) = board_dir.file_name().and_then(|n| n.to_str()) {
181 let status = if available_boards.contains(board_name) {
182 "1"
183 } else {
184 "0"
185 };
186 if index > 0 {
187 writeln!(file, ",")?;
188 }
189 write!(file, " \"{board_name}\",{status}")?;
190 }
191 }
192
193 writeln!(file, "\n}}")?;
194
195 Ok(())
196}
197
198fn natural_sort(paths: &mut [PathBuf]) {
199 paths.sort_by(|a, b| {
200 let a_name = a.file_name().and_then(|n| n.to_str()).unwrap_or("");
201 let b_name = b.file_name().and_then(|n| n.to_str()).unwrap_or("");
202
203 let a_num = extract_number(a_name);
205 let b_num = extract_number(b_name);
206
207 a_num.cmp(&b_num)
208 });
209}
210
211fn extract_number(s: &str) -> u32 {
212 s.trim_start_matches("board_").parse::<u32>().unwrap_or(0)
213}
214
215#[derive(serde::Deserialize)]
216struct BoardRawConfig {
217 mat_file: String,
218}
219
220#[derive(serde::Deserialize)]
221struct MatRawConfig {
222 material: MaterialData,
223}
224
225#[derive(serde::Deserialize)]
226struct MaterialData {
227 texture: String,
228}
229
230#[must_use]
234pub fn get_board_texture_path(board_dir: &Path) -> Option<PathBuf> {
235 let board_config_path = board_dir.join("boardcnf.txt");
236 if !board_config_path.exists() {
237 return None;
238 }
239
240 let config: BoardRawConfig = match supreme_parsing::from_path(&board_config_path) {
241 Ok(c) => c,
242 Err(e) => {
243 eprintln!(
244 "Error parsing board config {}: {}",
245 board_config_path.display(),
246 e
247 );
248 return None;
249 }
250 };
251 let mat_file_val = &config.mat_file;
252
253 let mat_file_path = if mat_file_val.contains('/') || mat_file_val.contains('\\') {
256 let p = Path::new(mat_file_val);
258 if let Some(filename) = p.file_name() {
259 board_dir.join(filename)
260 } else {
261 board_dir.join(mat_file_val)
262 }
263 } else {
264 board_dir.join(mat_file_val)
265 };
266
267 if !mat_file_path.exists() {
268 eprintln!("Material file not found: {}", mat_file_path.display());
269 return None;
270 }
271
272 let mat_map: MatRawConfig = match supreme_parsing::from_path(&mat_file_path) {
273 Ok(m) => m,
274 Err(e) => {
275 eprintln!(
276 "Error parsing material file {}: {}",
277 mat_file_path.display(),
278 e
279 );
280 return None;
281 }
282 };
283 Some(PathBuf::from(mat_map.material.texture))
284}