rpfm_extensions/search/anim_fragment_battle.rs
1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11use getset::{Getters, MutGetters, Setters};
12use serde_derive::{Deserialize, Serialize};
13
14use rpfm_lib::files::anim_fragment_battle::AnimFragmentBattle;
15
16use super::{find_in_string, MatchingMode, replace_match_string, Replaceable, SearchSource, Searchable};
17
18//-------------------------------------------------------------------------------//
19// Enums & Structs
20//-------------------------------------------------------------------------------//
21
22/// This struct represents all the matches of the global search within an Anim Fragment Battle File.
23#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
24#[getset(get = "pub", get_mut = "pub", set = "pub")]
25pub struct AnimFragmentBattleMatches {
26
27 /// The path of the file.
28 path: String,
29
30 /// The search source that produced these matches.
31 #[serde(default)]
32 source: SearchSource,
33
34 /// The container name (pack file name) this file belongs to.
35 #[serde(default)]
36 container_name: String,
37
38 /// The list of matches within the file.
39 matches: Vec<AnimFragmentBattleMatch>,
40}
41
42/// This struct represents a match within an Anim Fragment Battle File.
43#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
44#[getset(get = "pub", get_mut = "pub")]
45pub struct AnimFragmentBattleMatch {
46
47 /// If the match corresponds to a skeleton name value.
48 skeleton_name: bool,
49
50 /// If the match corresponds to a table name value.
51 table_name: bool,
52
53 /// If the match corresponds to a mount table name value.
54 mount_table_name: bool,
55
56 /// If the match corresponds to a unmount table name value.
57 unmount_table_name: bool,
58
59 /// If the match corresponds to a locomotion table name value.
60 locomotion_graph: bool,
61
62 /// If the match corresponds to an entry in the table view.
63 entry: Option<(usize, Option<(usize, bool, bool, bool)>, bool, bool, bool, bool, bool)>,
64
65 /// Byte where the match starts.
66 start: usize,
67
68 /// Byte where the match ends.
69 end: usize,
70
71 /// The contents of the matched cell.
72 text: String,
73}
74
75//-------------------------------------------------------------------------------//
76// Implementations
77//-------------------------------------------------------------------------------//
78
79impl Searchable for AnimFragmentBattle {
80 type SearchMatches = AnimFragmentBattleMatches;
81
82 fn search(&self, file_path: &str, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> AnimFragmentBattleMatches {
83 let mut matches = AnimFragmentBattleMatches::new(file_path);
84
85 match matching_mode {
86 MatchingMode::Regex(regex) => {
87 for entry_match in regex.find_iter(self.skeleton_name()) {
88 matches.matches.push(
89 AnimFragmentBattleMatch::new(
90 true,
91 false,
92 false,
93 false,
94 false,
95 None,
96 entry_match.start(),
97 entry_match.end(),
98 self.skeleton_name().to_owned()
99 )
100 );
101 }
102
103 for entry_match in regex.find_iter(self.table_name()) {
104 matches.matches.push(
105 AnimFragmentBattleMatch::new(
106 false,
107 true,
108 false,
109 false,
110 false,
111 None,
112 entry_match.start(),
113 entry_match.end(),
114 self.table_name().to_owned()
115 )
116 );
117 }
118
119 for entry_match in regex.find_iter(self.mount_table_name()) {
120 matches.matches.push(
121 AnimFragmentBattleMatch::new(
122 false,
123 false,
124 true,
125 false,
126 false,
127 None,
128 entry_match.start(),
129 entry_match.end(),
130 self.mount_table_name().to_owned()
131 )
132 );
133 }
134
135 for entry_match in regex.find_iter(self.unmount_table_name()) {
136 matches.matches.push(
137 AnimFragmentBattleMatch::new(
138 false,
139 false,
140 false,
141 true,
142 false,
143 None,
144 entry_match.start(),
145 entry_match.end(),
146 self.unmount_table_name().to_owned()
147 )
148 );
149 }
150
151 for entry_match in regex.find_iter(self.locomotion_graph()) {
152 matches.matches.push(
153 AnimFragmentBattleMatch::new(
154 false,
155 false,
156 false,
157 false,
158 true,
159 None,
160 entry_match.start(),
161 entry_match.end(),
162 self.locomotion_graph().to_owned()
163 )
164 );
165 }
166
167 for (row, entry) in self.entries().iter().enumerate() {
168 for (subrow, anim_refs) in entry.anim_refs().iter().enumerate() {
169 for entry_match in regex.find_iter(anim_refs.file_path()) {
170 matches.matches.push(
171 AnimFragmentBattleMatch::new(
172 false,
173 false,
174 false,
175 false,
176 false,
177 Some((row, Some((subrow, true, false, false)), false, false, false, false, false)),
178 entry_match.start(),
179 entry_match.end(),
180 anim_refs.file_path().to_owned()
181 )
182 );
183 }
184
185 for entry_match in regex.find_iter(anim_refs.meta_file_path()) {
186 matches.matches.push(
187 AnimFragmentBattleMatch::new(
188 false,
189 false,
190 false,
191 false,
192 false,
193 Some((row, Some((subrow, false, true, false)), false, false, false, false, false)),
194 entry_match.start(),
195 entry_match.end(),
196 anim_refs.meta_file_path().to_owned()
197 )
198 );
199 }
200
201 for entry_match in regex.find_iter(anim_refs.snd_file_path()) {
202 matches.matches.push(
203 AnimFragmentBattleMatch::new(
204 false,
205 false,
206 false,
207 false,
208 false,
209 Some((row, Some((subrow, false, false, true)), false, false, false, false, false)),
210 entry_match.start(),
211 entry_match.end(),
212 anim_refs.snd_file_path().to_owned()
213 )
214 );
215 }
216 }
217
218 for entry_match in regex.find_iter(entry.filename()) {
219 matches.matches.push(
220 AnimFragmentBattleMatch::new(
221 false,
222 false,
223 false,
224 false,
225 false,
226 Some((row, None, true, false, false, false, false)),
227 entry_match.start(),
228 entry_match.end(),
229 entry.filename().to_owned()
230 )
231 );
232 }
233
234 for entry_match in regex.find_iter(entry.metadata()) {
235 matches.matches.push(
236 AnimFragmentBattleMatch::new(
237 false,
238 false,
239 false,
240 false,
241 false,
242 Some((row, None, false, true, false, false, false)),
243 entry_match.start(),
244 entry_match.end(),
245 entry.metadata().to_owned()
246 )
247 );
248 }
249
250 for entry_match in regex.find_iter(entry.metadata_sound()) {
251 matches.matches.push(
252 AnimFragmentBattleMatch::new(
253 false,
254 false,
255 false,
256 false,
257 false,
258 Some((row, None, false, false, true, false, false)),
259 entry_match.start(),
260 entry_match.end(),
261 entry.metadata_sound().to_owned()
262 )
263 );
264 }
265
266 for entry_match in regex.find_iter(entry.skeleton_type()) {
267 matches.matches.push(
268 AnimFragmentBattleMatch::new(
269 false,
270 false,
271 false,
272 false,
273 false,
274 Some((row, None, false, false, false, true, false)),
275 entry_match.start(),
276 entry_match.end(),
277 entry.skeleton_type().to_owned()
278 )
279 );
280 }
281
282 for entry_match in regex.find_iter(entry.uk_4()) {
283 matches.matches.push(
284 AnimFragmentBattleMatch::new(
285 false,
286 false,
287 false,
288 false,
289 false,
290 Some((row, None, false, false, false, false, true)),
291 entry_match.start(),
292 entry_match.end(),
293 entry.uk_4().to_owned()
294 )
295 );
296 }
297 }
298 }
299
300 MatchingMode::Pattern(regex) => {
301 let pattern = if case_sensitive || regex.is_some() {
302 pattern.to_owned()
303 } else {
304 pattern.to_lowercase()
305 };
306
307 for (start, end, _) in &find_in_string(self.skeleton_name(), &pattern, case_sensitive, regex) {
308 matches.matches.push(
309 AnimFragmentBattleMatch::new(
310 true,
311 false,
312 false,
313 false,
314 false,
315 None,
316 *start,
317 *end,
318 self.skeleton_name().to_owned()
319 )
320 );
321 }
322
323 for (start, end, _) in &find_in_string(self.table_name(), &pattern, case_sensitive, regex) {
324 matches.matches.push(
325 AnimFragmentBattleMatch::new(
326 false,
327 true,
328 false,
329 false,
330 false,
331 None,
332 *start,
333 *end,
334 self.table_name().to_owned()
335 )
336 );
337 }
338
339 for (start, end, _) in &find_in_string(self.mount_table_name(), &pattern, case_sensitive, regex) {
340 matches.matches.push(
341 AnimFragmentBattleMatch::new(
342 false,
343 false,
344 true,
345 false,
346 false,
347 None,
348 *start,
349 *end,
350 self.mount_table_name().to_owned()
351 )
352 );
353 }
354
355 for (start, end, _) in &find_in_string(self.unmount_table_name(), &pattern, case_sensitive, regex) {
356 matches.matches.push(
357 AnimFragmentBattleMatch::new(
358 false,
359 false,
360 false,
361 true,
362 false,
363 None,
364 *start,
365 *end,
366 self.unmount_table_name().to_owned()
367 )
368 );
369 }
370
371 for (start, end, _) in &find_in_string(self.locomotion_graph(), &pattern, case_sensitive, regex) {
372 matches.matches.push(
373 AnimFragmentBattleMatch::new(
374 false,
375 false,
376 false,
377 false,
378 true,
379 None,
380 *start,
381 *end,
382 self.locomotion_graph().to_owned()
383 )
384 );
385 }
386
387 for (row, entry) in self.entries().iter().enumerate() {
388 for (subrow, anim_refs) in entry.anim_refs().iter().enumerate() {
389 for (start, end, _) in &find_in_string(anim_refs.file_path(), &pattern, case_sensitive, regex) {
390 matches.matches.push(
391 AnimFragmentBattleMatch::new(
392 false,
393 false,
394 false,
395 false,
396 false,
397 Some((row, Some((subrow, true, false, false)), false, false, false, false, false)),
398 *start,
399 *end,
400 anim_refs.file_path().to_owned()
401 )
402 );
403 }
404
405 for (start, end, _) in &find_in_string(anim_refs.meta_file_path(), &pattern, case_sensitive, regex) {
406 matches.matches.push(
407 AnimFragmentBattleMatch::new(
408 false,
409 false,
410 false,
411 false,
412 false,
413 Some((row, Some((subrow, false, true, false)), false, false, false, false, false)),
414 *start,
415 *end,
416 anim_refs.meta_file_path().to_owned()
417 )
418 );
419 }
420
421 for (start, end, _) in &find_in_string(anim_refs.snd_file_path(), &pattern, case_sensitive, regex) {
422 matches.matches.push(
423 AnimFragmentBattleMatch::new(
424 false,
425 false,
426 false,
427 false,
428 false,
429 Some((row, Some((subrow, false, false, true)), false, false, false, false, false)),
430 *start,
431 *end,
432 anim_refs.snd_file_path().to_owned()
433 )
434 );
435 }
436 }
437
438 for (start, end, _) in &find_in_string(entry.filename(), &pattern, case_sensitive, regex) {
439 matches.matches.push(
440 AnimFragmentBattleMatch::new(
441 false,
442 false,
443 false,
444 false,
445 false,
446 Some((row, None, true, false, false, false, false)),
447 *start,
448 *end,
449 entry.filename().to_owned()
450 )
451 );
452 }
453
454 for (start, end, _) in &find_in_string(entry.metadata(), &pattern, case_sensitive, regex) {
455 matches.matches.push(
456 AnimFragmentBattleMatch::new(
457 false,
458 false,
459 false,
460 false,
461 false,
462 Some((row, None, false, true, false, false, false)),
463 *start,
464 *end,
465 entry.metadata().to_owned()
466 )
467 );
468 }
469
470 for (start, end, _) in &find_in_string(entry.metadata_sound(), &pattern, case_sensitive, regex) {
471 matches.matches.push(
472 AnimFragmentBattleMatch::new(
473 false,
474 false,
475 false,
476 false,
477 false,
478 Some((row, None, false, false, true, false, false)),
479 *start,
480 *end,
481 entry.metadata_sound().to_owned()
482 )
483 );
484 }
485
486 for (start, end, _) in &find_in_string(entry.skeleton_type(), &pattern, case_sensitive, regex) {
487 matches.matches.push(
488 AnimFragmentBattleMatch::new(
489 false,
490 false,
491 false,
492 false,
493 false,
494 Some((row, None, false, false, false, true, false)),
495 *start,
496 *end,
497 entry.skeleton_type().to_owned()
498 )
499 );
500 }
501
502 for (start, end, _) in &find_in_string(entry.uk_4(), &pattern, case_sensitive, regex) {
503 matches.matches.push(
504 AnimFragmentBattleMatch::new(
505 false,
506 false,
507 false,
508 false,
509 false,
510 Some((row, None, false, false, false, false, true)),
511 *start,
512 *end,
513 entry.uk_4().to_owned()
514 )
515 );
516 }
517 }
518 }
519 }
520
521 matches
522 }
523}
524
525impl Replaceable for AnimFragmentBattle {
526
527 fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &AnimFragmentBattleMatches) -> bool {
528 let mut edited = false;
529
530 // NOTE: Due to changes in index positions, we need to do this in reverse.
531 // Otherwise we may cause one edit to generate invalid indexes for the next matches.
532 for search_match in search_matches.matches().iter().rev() {
533 edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, self);
534 }
535
536 edited
537 }
538}
539
540impl AnimFragmentBattleMatches {
541
542 /// This function creates a new `AnimFragmentBattleMatches` for the provided path.
543 pub fn new(path: &str) -> Self {
544 Self {
545 path: path.to_owned(),
546 matches: vec![],
547 source: SearchSource::default(),
548 container_name: String::new(),
549 }
550 }
551}
552
553impl AnimFragmentBattleMatch {
554
555 /// This function creates a new `AnimFragmentBattleMatch` with the provided data.
556 pub fn new(skeleton_name: bool, table_name: bool, mount_table_name: bool, unmount_table_name: bool, locomotion_graph: bool, entry: Option<(usize, Option<(usize, bool, bool, bool)>, bool, bool, bool, bool, bool)>, start: usize, end: usize, text: String) -> Self {
557 Self {
558 skeleton_name,
559 table_name,
560 mount_table_name,
561 unmount_table_name,
562 locomotion_graph,
563 entry,
564 start,
565 end,
566 text,
567 }
568 }
569
570 /// This function replaces all the matches in the provided data.
571 fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut AnimFragmentBattle) -> bool {
572
573 // Get all the previous data and references of data to manipulate here, so we don't duplicate a lot of code per-field in the match mode part.
574 let (previous_data, current_data) = {
575 if self.skeleton_name {
576 (data.skeleton_name().to_owned(), data.skeleton_name_mut())
577 } else if self.table_name {
578 (data.table_name().to_owned(), data.table_name_mut())
579 } else if self.mount_table_name {
580 (data.mount_table_name().to_owned(), data.mount_table_name_mut())
581 } else if self.unmount_table_name {
582 (data.unmount_table_name().to_owned(), data.unmount_table_name_mut())
583 } else if self.locomotion_graph {
584 (data.locomotion_graph().to_owned(), data.locomotion_graph_mut())
585 } else if let Some((row, anim_ref, filename, metadata, metadata_sound, skeleton_type, uk_4)) = self.entry {
586 match data.entries_mut().get_mut(row) {
587 Some(entry) => {
588 if let Some((subrow, file_path, meta_file_path, snd_file_path)) = anim_ref {
589 match entry.anim_refs_mut().get_mut(subrow) {
590 Some(subentry) => {
591 if file_path {
592 (subentry.file_path().to_owned(), subentry.file_path_mut())
593 } else if meta_file_path {
594 (subentry.meta_file_path().to_owned(), subentry.meta_file_path_mut())
595 } else if snd_file_path {
596 (subentry.snd_file_path().to_owned(), subentry.snd_file_path_mut())
597 } else {
598 return false;
599 }
600 }
601 None => return false,
602 }
603 } else if filename {
604 (entry.filename().to_owned(), entry.filename_mut())
605 } else if metadata {
606 (entry.metadata().to_owned(), entry.metadata_mut())
607 } else if metadata_sound {
608 (entry.metadata_sound().to_owned(), entry.metadata_sound_mut())
609 } else if skeleton_type {
610 (entry.skeleton_type().to_owned(), entry.skeleton_type_mut())
611 } else if uk_4 {
612 (entry.uk_4().to_owned(), entry.uk_4_mut())
613 } else {
614 return false;
615 }
616 }
617 None => return false,
618 }
619 }
620
621 // This is an error.
622 else {
623 return false
624 }
625 };
626
627 replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, current_data)
628 }
629}