Skip to main content

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}